Compare commits
16
Commits
v0.2.1
..
5106a393f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5106a393f1 | ||
|
|
75ecf7717a | ||
|
|
7ab1b74eb4 | ||
|
|
d07fbfabb1 | ||
|
|
0bdc409ed0 | ||
|
|
a39d0dbb36 | ||
|
|
3027fd51ec | ||
|
|
a659c9dcc7 | ||
|
|
d2d1092344 | ||
|
|
fc589c9130 | ||
|
|
3321d9db25 | ||
|
|
03b3b59e6f | ||
|
|
59e0831ad2 | ||
|
|
2dc72255fe | ||
|
|
ed8221ae8a | ||
|
|
1ed4dd5a73 |
@@ -0,0 +1,171 @@
|
||||
# Engineering Workflow
|
||||
|
||||
Область: пять независимых checkout workspace (`main`, `frontend`, `backend`,
|
||||
`docs`, `specs`). Репозиторий `deploy` существует в Gitea отдельно и применяет
|
||||
свои локальные правила. Этот документ — единственный владелец общего процесса;
|
||||
локальные AGENTS и conventions определяют технические проверки и ограничения.
|
||||
|
||||
## Issue Management
|
||||
|
||||
| Переход | Условие входа → действие → условие выхода |
|
||||
| --- | --- |
|
||||
| Request → Issue | До изменения файлов/конфигурации/спецификаций определить затронутые repos, найти по смыслу открытые и закрытые Issues, проверить актуальность и scope. Взять подходящую открытую Issue (закрытую переоткрыть только для действительно незавершённой работы) либо создать одну новую. Чтение, анализ и предложение изменений разрешены без Issue. |
|
||||
| Issue → Analysis | В Issue должны быть результат в title, контекст/проблема и ожидаемое поведение, проверяемые Acceptance Criteria. Уточнить неизвестное до зависимого решения. Выбрать тип и приоритет; назначить исполнителя, если известен; milestone только при реальном плане, dependencies/related Issues только при наличии. |
|
||||
| Analysis → Delivery | Определить область и риски, выбрать lightweight или SDD ниже. Если AC или owner меняются, сначала обновить соответствующую Issue/согласование. |
|
||||
|
||||
Типы: `feature`, `bug`, `refactor`, `tech-debt`, `docs`, `test`, `ci`,
|
||||
`chore`, `performance`. Существующие org labels отражают их без требования
|
||||
один-к-одному: `Kind/Feature`, `Kind/Bug`, `Kind/Refactor`,
|
||||
`Kind/Documentation`, `Kind/Testing`, `Kind/Enhancement` (performance);
|
||||
repo-local `Kind/Process` подходит для ci, chore и process/tech-debt.
|
||||
Дополнительные `Kind/Security`, `Kind/Investigation`, `Kind/Dependencies`
|
||||
(последние два — repo-local) — по ситуации. Priority: существующие
|
||||
`Priority/Critical|High|Medium|Low`.
|
||||
Gitea assignee, milestone и state не дублируются labels. Для назначаемых
|
||||
Issue и PR выбирай `hermes-agent`; незанятую задачу не назначай формально.
|
||||
Шаблоны ускоряют ввод, но не доказывают качество заполнения.
|
||||
|
||||
Один координирующий Issue достаточен, если результат и AC едины, одна команда
|
||||
может принять работу целиком и отдельные repos не требуют независимой поставки.
|
||||
Иначе Issue в каждом независимо принимаемом repo; ссылаться друг на друга и
|
||||
указать порядок зависимостей. Номера `#27` локальны: для чужого repo использовать
|
||||
`WealthLedger/main#27` или полный URL. Не заводить дубль только из-за второго PR.
|
||||
|
||||
## Git Branching
|
||||
|
||||
| Переход | Условие входа → действие → условие выхода |
|
||||
| --- | --- |
|
||||
| Analysis → Branch | Issue и AC определены; проверить `git status`, текущую ветку и `origin/develop`. Использовать существующую подходящую ветку либо создать одну от актуального `develop` каждого affected repo. Чужие изменения не переносить. |
|
||||
| Branch → Implementation | `<type>/<issue-number>-<english-kebab-description>`; номер принадлежит указанной в PR Issue (для cross-repo — координатору). Примеры: `feature/123-import-transactions`, `fix/124-invalid-balance`, `refactor/125-simplify-api-client`, `docs/126-update-architecture`. |
|
||||
|
||||
Префиксы: feature → `feature`, bug → `fix`, refactor/tech-debt →
|
||||
`refactor`, docs → `docs`, test → `test`, ci → `ci`, chore → `chore`,
|
||||
performance → `perf`. Номер обязателен для новых рабочих веток; короткий
|
||||
английский slug в kebab-case. Ветки `main`/`develop` не место для прямой
|
||||
разработки. Release, Renovate и исторические ветки подчиняются существующему
|
||||
release/automation процессу, а не переименовываются.
|
||||
|
||||
Рабочий PR сливается squash или rebase-merge; merge-коммит не используется.
|
||||
`main` обновляется только fast-forward из `develop`, поэтому `origin/main`
|
||||
всегда предок `origin/develop`. Срочный fix — обычная ветка `fix/*` от
|
||||
`develop`; отдельного hotfix-пути от `main` в обычном процессе нет.
|
||||
Merge policy, версии, теги и исключения принадлежат
|
||||
[процессу релиза](../../README.md#релизы-и-ветки).
|
||||
|
||||
## Commit Conventions
|
||||
|
||||
| Переход | Условие входа → действие → условие выхода |
|
||||
| --- | --- |
|
||||
| Implementation → Commit | Одна связанная задача на commit, проверить staged paths/diff. Заголовок Conventional Commits с осмысленным subject и ссылкой на Issue: `feat(portfolio): add transaction import (#123)` или `ci: align gates (WealthLedger/main#27)`. Scope только при полезной области. |
|
||||
|
||||
Commitlint требует распознаваемую conventional-commits-parser ссылку, но не
|
||||
проверяет числовой формат, существование Issue, её смысл или корректный repo.
|
||||
Использовать `#N` только для локальной Issue и `WealthLedger/repo#N` для чужой;
|
||||
это формат команды, проверяемый при review. Не добавлять custom validator
|
||||
ради более строгой регулярной проверки. Без `fixes`,
|
||||
`closes`, `resolves` в сообщении: не закрывать задачу первым коммитом.
|
||||
Ссылка в footer тоже принимается commitlint; размещение в заголовке остаётся
|
||||
правилом команды. Если заголовок PR после squash станет commit title, оставить
|
||||
в нём ссылку. `chore(release): X.Y.Z` освобождён только при точном SemVer
|
||||
заголовке release-it. Стандартные Gitea merge commits освобождаются по точному
|
||||
шаблону merge message; sync PR используют обычный review/merge process.
|
||||
Шаблонные сообщения `Revert`/`fixup!`/`squash!` не освобождаются: revert
|
||||
оформляй как `revert: … (#N)`. Историю не переписывать ради формата.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
| Переход | Условие входа → действие → условие выхода |
|
||||
| --- | --- |
|
||||
| Validation → PR | Завершить AC и релевантные локальные проверки; PR в `develop` из рабочей ветки, только связанный diff. В теле: Summary, Linked Issue, Changes, Verification (команда/результат/ограничения), Breaking Changes при наличии. Для cross-repo PR указывать полный reference и связанные PR. |
|
||||
| PR → Review → Merge | Дождаться required checks на финальной ревизии, проверить AC, значимые изменения — review владельца/специалиста. При замечаниях исправить и обновить evidence. Merge только по политике repo. |
|
||||
| Merge → Issue Closed | Закрыть Issue после принятия **всех** связанных delivery areas, документации и при SDD — обязательной verification/archive; multi-PR Issue остаётся открытой до последнего результата. |
|
||||
|
||||
Перед передачей PR на review перенеси в него тип и приоритет связанной Issue,
|
||||
назначь `hermes-agent`, привяжи к подходящему milestone (если он есть) и к
|
||||
`WealthLedger — Delivery Board`, колонка «На проверке». Для cross-repo PR
|
||||
используй milestone его собственного repo. Колонку меняй вместе со стадией:
|
||||
при блокировке — «Заблокировано», после принятия — «Готово». Не вводи label
|
||||
этапа вместо встроенной колонки проекта.
|
||||
|
||||
Для SDD отдельные pre-archive checks/semantic review выполняются до archive по
|
||||
локальной schema; порядок merge/archive согласуется с конкретным change, но
|
||||
Issue закрывается только после обоих событий. Не подменять OpenSpec approval
|
||||
обычным PR review.
|
||||
|
||||
## SDD Integration
|
||||
|
||||
Lightweight: локальный bugfix с известным ожидаемым поведением, docs-only,
|
||||
рефакторинг без изменения контрактов/наблюдаемого поведения и малые CI-правки:
|
||||
`Issue → Analysis → Branch → Implementation → Validation → PR`.
|
||||
|
||||
SDD: новая функциональность, изменение публичного API, бизнес-правил, поведения
|
||||
нескольких компонентов, архитектуры или неоднозначные требования. После Issue
|
||||
уточнить намерение и запросить согласование Change Owner на вход в OpenSpec;
|
||||
OpenSpec lifecycle применяется только по явному запросу/решению владельца.
|
||||
Использовать **только** store `wealth-ledger-specs` и его локальный lifecycle:
|
||||
Proposal → Delta Specs → System Analysis → Design + QA Analysis → Docs Impact →
|
||||
Tasks → Apply (для legacy change — его собственная schema). Явные approvals
|
||||
перед downstream artifacts и Apply не заменяются Issue/PR. Ветка specs может
|
||||
появиться для planning; ветки реализации — когда Apply разрешён, без
|
||||
насильственной общей точки создания всех веток. Связать Issue ↔ change ID ↔
|
||||
PR текстовыми ссылками; после проверки применить штатные sync/archive и
|
||||
completion evidence согласно локальной schema. Число файлов не определяет SDD.
|
||||
Если владелец не согласовал вход в SDD, не начинать зависящую реализацию
|
||||
контракта/семантики и явно сообщить блокировку.
|
||||
|
||||
## AI Agent Responsibilities
|
||||
|
||||
Перед edit — поиск подходящей Issue, AC, workflow, branch/status каждого repo;
|
||||
затем только согласованный scope. По завершении — итоговый diff, проверенные
|
||||
команды и ограничения, подготовка commit/PR по запросу и статус Issue. Чтение
|
||||
не требует Issue. Если Gitea недоступна, сообщить об этом до edit и запросить
|
||||
issue reference; не выдавать локальную заметку за Issue. Агенты используют
|
||||
локальные инструкции репозитория и его CI inputs, не полагаются на соседние
|
||||
checkout как обязательную build-зависимость. Этот источник доступен в main;
|
||||
при недоступной ссылке самостоятельный checkout сохраняет обязательный минимум
|
||||
в своём AGENTS и не блокирует локальную работу из-за отсутствия root checkout.
|
||||
|
||||
## Quality Gates
|
||||
|
||||
Итерации: узкие проверки по риску; после логического изменения — релевантные
|
||||
тесты; перед передачей результата — локальные обязательные gates данного repo
|
||||
на итоговом diff; CI — независимые полные проверки. Не повторять дорогой verify
|
||||
после каждого файла; повторить при изменении значимых входов. `main`: `commits`;
|
||||
остальные: локальные `verify`, `security`, `commits` согласно фактическим CI.
|
||||
Frontend/backend запускают их как jobs в едином `verify.yml`, docs/specs —
|
||||
как отдельные workflows; второго CI ради одного правила не требуется.
|
||||
Для Markdown-only не запускать application build. Результат lint не заменяет
|
||||
семантическую проверку. Обязательность merge checks зависит от защиты ветки.
|
||||
|
||||
| Правило | Уровень на момент принятия документа |
|
||||
| --- | --- |
|
||||
| Conventional Commits и parser-recognized Issue reference в PR title/commits | CI-проверка через существующий commitlint; блокировка merge **только если** check обязателен в branch protection. Не гарантирует numeric reference, существование или соответствие Issue. Точный release SemVer title и штатные Gitea merge messages исключены. Локальные hooks не гарантируют server-side. |
|
||||
| PR вместо прямого push; successful checks перед merge | Enforced только в repos/ветках, где Gitea branch protection настроена и проверена владельцем. YAML сам по себе не защищает ветку. |
|
||||
| Поиск актуальной Issue, AC, корректный repo, branch name/source, соответствие ссылки, SDD selection, scope, статус | Agent-guided; templates и текст не могут гарантировать соответствие. |
|
||||
| Значимое review, соответствие AC/контракту, исключения и closure после всех PR | Manual; approval может быть required в Gitea, если есть независимый reviewer. |
|
||||
|
||||
## Exceptions and Recovery
|
||||
|
||||
Срочный hotfix: немедленно завести/найти Issue, затем исправление в
|
||||
обычной ветке `fix/<issue>-<slug>` от актуального `develop`, PR в `develop`
|
||||
и релиз по существующему процессу. Отдельного пути от `main` нет: он сделал бы
|
||||
`main` не-предком `develop`. Если `develop` содержит незаконченную работу,
|
||||
быстрого фикса нет; обход требует явного решения и описан в
|
||||
[процессе релиза](../../README.md#ограничения-модели). Связать Issue
|
||||
в PR и сопровождающем коммите. Не отменять Issue или проверки из-за срочности.
|
||||
Если CI красный или PR устарел — исправить в той же ветке и перепроверить
|
||||
актуальный HEAD; если AC изменились — обновить Issue и при SDD вернуть change
|
||||
к затронутому approval gate. При недоступной Gitea продолжать только анализ.
|
||||
Изменение PR title после зелёного CI: проверить, что check пересчитан;
|
||||
если `edited` trigger у существующих checks отсутствует, reviewer проверяет
|
||||
текущий title вручную. Новые правила не применяются к историческим коммитам
|
||||
и merge-коммитам, уже находящимся в истории.
|
||||
|
||||
Примеры: фича с новым поведением — Issue → согласованный OpenSpec change →
|
||||
ветки Apply → PR каждого affected repo → archive → закрытие Issue. Локальный
|
||||
bugfix — Issue → `fix/124-invalid-balance` → регрессия → PR → закрытие.
|
||||
Cross-repo контракт — одна Issue с общими AC, `backend` и `frontend` PR с
|
||||
`WealthLedger/main#27` и взаимными ссылками; если независимые acceptance/owners,
|
||||
отдельные связанные Issues. Рефакторинг без изменения поведения — lightweight
|
||||
`refactor/125-simplify-api-client`. Hotfix — Issue → `fix/*` от `develop` →
|
||||
PR в `develop` → релиз → закрытие.
|
||||
+10
-4
@@ -2,11 +2,13 @@
|
||||
|
||||
- Выполняй Git-команды из репозитория владельца: root, frontend, backend,
|
||||
docs и specs имеют независимые history, index и branches.
|
||||
- Новая работа: ветка `codex/<task>` от актуального `develop`, затем PR в `develop`.
|
||||
- Новая работа: ветка с номером Issue по [Engineering Workflow](engineering-workflow.md#git-branching)
|
||||
от актуального `develop`, затем PR в `develop`.
|
||||
Сохраняй чужие staged/unstaged/untracked изменения; не переноси их в свой commit.
|
||||
- Commit содержит одну связанную задачу и её необходимые проверки/документацию.
|
||||
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
|
||||
- Commits и заголовки PR — Conventional Commits; допустим `spec:`.
|
||||
- Commits и заголовки PR — Conventional Commits со ссылкой на Issue;
|
||||
допустим `spec:`. Исключения release — в README.
|
||||
Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
|
||||
- Перед commit проверь `git diff --cached --name-status` и сам staged diff
|
||||
в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore):
|
||||
@@ -14,7 +16,11 @@
|
||||
- Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или
|
||||
перенос опубликованных тегов как обычную очистку. Для опасной операции нужны
|
||||
явная необходимость, сохранение данных и соответствующая авторизация.
|
||||
- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop,
|
||||
release-конфигурация требуют чтения
|
||||
- Обычный PR сливается squash или rebase-merge; merge-коммит не используется.
|
||||
`main` обновляется только fast-forward из `develop`, поэтому
|
||||
`origin/main` всегда предок `origin/develop`. Проверка:
|
||||
`git merge-base --is-ancestor origin/main origin/develop`.
|
||||
- Release, hotfix и release-конфигурация требуют чтения
|
||||
[процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy,
|
||||
версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай.
|
||||
Отдельной синхронизации `main → develop` в обычном процессе нет.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Неверное поведение
|
||||
---
|
||||
## Проблема и шаги воспроизведения
|
||||
|
||||
## Факт / ожидание
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ]
|
||||
|
||||
## Окружение / ссылки (если применимо)
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Engineering Task
|
||||
about: CI, тесты, документация, процесс и прочее
|
||||
---
|
||||
## Контекст и результат
|
||||
|
||||
## Scope / связанные Issues (если есть)
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ]
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Новое наблюдаемое поведение
|
||||
---
|
||||
## Зачем и какой результат
|
||||
|
||||
## Ожидаемое поведение
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ]
|
||||
|
||||
## Зависимости / связанные Issues (если есть)
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: Refactoring / Technical Debt
|
||||
about: Улучшение без нового поведения
|
||||
---
|
||||
## Проблема и результат
|
||||
|
||||
## Сохраняемые инварианты / границы
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ]
|
||||
|
||||
## Зависимости / ссылки (если есть)
|
||||
@@ -0,0 +1,11 @@
|
||||
## Summary
|
||||
|
||||
## Linked Issue
|
||||
Refs
|
||||
|
||||
## Changes
|
||||
|
||||
## Verification
|
||||
- Команда / результат / ограничения:
|
||||
|
||||
## Breaking Changes (если есть)
|
||||
@@ -23,4 +23,8 @@ jobs:
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
printf '%s\n' "$PR_TITLE" | npm exec --no -- commitlint --strict
|
||||
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||
if [[ "$PR_TITLE" =~ ^chore\(release\):\ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
npm exec --no -- commitlint --strict --from "$HEAD_SHA^" --to "$HEAD_SHA"
|
||||
else
|
||||
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||
fi
|
||||
|
||||
+21
@@ -12,6 +12,7 @@
|
||||
!/.ai/conventions/
|
||||
/.ai/conventions/*
|
||||
!/.ai/conventions/git.md
|
||||
!/.ai/conventions/engineering-workflow.md
|
||||
!/.ai/conventions/dependencies.md
|
||||
!/.ai/conventions/code-health.md
|
||||
!/.ai/conventions/testing.md
|
||||
@@ -35,3 +36,23 @@
|
||||
!/.gitea/workflows/
|
||||
/.gitea/workflows/*
|
||||
!/.gitea/workflows/commits.yml
|
||||
!/.gitea/ISSUE_TEMPLATE/
|
||||
/.gitea/ISSUE_TEMPLATE/*
|
||||
!/.gitea/ISSUE_TEMPLATE/feature.md
|
||||
!/.gitea/ISSUE_TEMPLATE/bug.md
|
||||
!/.gitea/ISSUE_TEMPLATE/refactor.md
|
||||
!/.gitea/ISSUE_TEMPLATE/engineering.md
|
||||
!/.gitea/PULL_REQUEST_TEMPLATE.md
|
||||
!/audits/
|
||||
/audits/*
|
||||
!/audits/2026-10-11/
|
||||
/audits/2026-10-11/*
|
||||
!/audits/2026-10-11/README.md
|
||||
!/audits/2026-10-11/triage.md
|
||||
!/audits/2026-10-11/reports/
|
||||
/audits/2026-10-11/reports/*
|
||||
!/audits/2026-10-11/reports/[0-9][0-9]-*.md
|
||||
!/audits/2026-10-11/reports/previous/
|
||||
/audits/2026-10-11/reports/previous/*
|
||||
!/audits/2026-10-11/reports/previous/complexity-audit.md
|
||||
!/audits/2026-10-11/reports/previous/technical-audit-2026-10-11.md
|
||||
|
||||
@@ -12,6 +12,11 @@ Root tracking ограничен allowlist; локальные данные и n
|
||||
Публичные API artifacts передаются явно; CI каждого repo использует локальные
|
||||
входы. Справочные источники не становятся обязательной цепочкой инструкций.
|
||||
|
||||
До изменения проекта найди подходящую Gitea Issue (или создай после поиска),
|
||||
проверь Acceptance Criteria, scope и рабочую ветку. Чтение/анализ допускаются
|
||||
без Issue. Маршрут, выбор lightweight/SDD и cross-repo ссылки — в
|
||||
[Engineering Workflow](.ai/conventions/engineering-workflow.md).
|
||||
|
||||
Начни с результата и affected areas. До edits проследи поведение/invariants,
|
||||
найди owner и 1–3 примера. Работай напрямую; OpenSpec/SDD — по явному запросу.
|
||||
|
||||
@@ -23,7 +28,10 @@ Root tracking ограничен allowlist; локальные данные и n
|
||||
| Git/branch/commit/PR/tracking | [Git](.ai/conventions/git.md) |
|
||||
| Release/hotfix | [README](README.md#релизы-и-ветки) |
|
||||
|
||||
Перед Node-командой активируй местную `.nvmrc`: `nvm use --silent`.
|
||||
Перед Node-командой активируй местную `.nvmrc`: если `nvm` не загружен
|
||||
в non-interactive shell, загрузи установленный `$HOME/.nvm/nvm.sh`, затем
|
||||
выполни `nvm use --silent` из root. Если `nvm.sh` недоступен, остановись;
|
||||
не используй системный Node вместо проектного.
|
||||
При новой границе и перед завершением повтори routing по staged/unstaged/new
|
||||
files; сохраняй чужие изменения. Missing source/конфликт требований сообщи
|
||||
до зависимого решения. После diff выполни
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.3](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.2...v0.2.3) (2026-10-18)
|
||||
|
||||
### Documentation
|
||||
|
||||
* archive technical audit 2026-10-11 in develop ([#36](https://git.personal.ksv741.ru/WealthLedger/main/issues/36)) ([d07fbfa](https://git.personal.ksv741.ru/WealthLedger/main/commit/d07fbfabb1295780952a554185989d2d5b5c8349))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* adopt linear git model without release-to-main branch ([#34](https://git.personal.ksv741.ru/WealthLedger/main/issues/34)) ([0bdc409](https://git.personal.ksv741.ru/WealthLedger/main/commit/0bdc409ed038fdf485be0867b8654f2f40e0b4d5))
|
||||
|
||||
## [0.2.2](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.1...v0.2.2) (2026-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* align workflow commitlint policy ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([03b3b59](https://git.personal.ksv741.ru/WealthLedger/main/commit/03b3b59e6ff2ba391f9dfc42a039a96d0d84c1c9))
|
||||
|
||||
### Documentation
|
||||
|
||||
* clarify root Node bootstrap [#24](https://git.personal.ksv741.ru/WealthLedger/main/issues/24) ([1ed4dd5](https://git.personal.ksv741.ru/WealthLedger/main/commit/1ed4dd5a73a250f24f452a26b5f5b0fcbb15f758))
|
||||
* document PR ownership and board stage ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([59e0831](https://git.personal.ksv741.ru/WealthLedger/main/commit/59e0831ad28d6990416e651f55d783b8d2ac98c2))
|
||||
* refine workflow conventions after review ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([fc589c9](https://git.personal.ksv741.ru/WealthLedger/main/commit/fc589c9130e90480c09f8fe8d47dd871aa05145e))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* establish engineering workflow ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([2dc7225](https://git.personal.ksv741.ru/WealthLedger/main/commit/2dc72255fe51bb48a208e10846d42d9f59e2c1af))
|
||||
|
||||
## [0.2.1](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.0...v0.2.1) (2026-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -83,7 +83,7 @@ Image rollback не восстанавливает старую базу. Пуб
|
||||
|
||||
## Подготовка и запуск
|
||||
|
||||
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
|
||||
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.22.1, npm 11.19).
|
||||
Во всех пяти репозиториях отдельно выполните
|
||||
`nvm install`, `nvm use --silent`, `npm ci`. В root и specs npm нужен для release-it
|
||||
и проверки сообщений коммитов.
|
||||
@@ -149,6 +149,7 @@ Allowlist root проверяется вручную по staged paths. Backend
|
||||
T-Bank. Полные команды и ограничения принадлежат README
|
||||
[frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md#проверка) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#проверка).
|
||||
Для изменения проекта начните с [AGENTS](AGENTS.md).
|
||||
Общий маршрут Issue → PR — [Engineering Workflow](.ai/conventions/engineering-workflow.md).
|
||||
|
||||
## Релизы и ветки
|
||||
|
||||
@@ -157,23 +158,40 @@ T-Bank. Полные команды и ограничения принадлеж
|
||||
`vX.Y.Z`. Root-репозиторий называется `main`, его ветки — `main` и `develop`.
|
||||
|
||||
`main` хранит принятые релизы, `develop` — интеграцию разработки. Новая задача:
|
||||
отдельная ветка от свежего `develop`, затем PR в `develop`. Для веток агента
|
||||
используется `codex/<task>`; также допустимы `feature/<task>` и `fix/<task>`.
|
||||
В `main` напрямую не пушить. Обычные PR можно squash-merge; релизные PR и
|
||||
обратную синхронизацию `main` с `develop` сливать с сохранением истории.
|
||||
отдельная [issue-linked ветка](.ai/conventions/engineering-workflow.md#git-branching)
|
||||
от свежего `develop`, затем PR в `develop`.
|
||||
В `main` напрямую не пушить. Рабочие PR сливать squash или rebase-merge;
|
||||
merge-коммиты запрещены. `main` получает изменения только fast-forward из
|
||||
`develop`, поэтому история обеих веток линейна:
|
||||
|
||||
```text
|
||||
<type>/<issue>-<slug> --squash--> develop --ff-only--> main --> tag vX.Y.Z
|
||||
```
|
||||
|
||||
Инвариант: `origin/main` всегда предок `origin/develop`. Проверка в любом repo:
|
||||
|
||||
```bash
|
||||
git fetch -q origin
|
||||
git merge-base --is-ancestor origin/main origin/develop && echo ok || echo BROKEN
|
||||
```
|
||||
|
||||
Инвариант нарушается только осознанным исключением
|
||||
([Ограничения модели](#ограничения-модели)); после него `develop` возвращают
|
||||
в согласованное состояние до следующего релиза.
|
||||
|
||||
Commits и заголовки PR — Conventional Commits. `feat:` повышает minor,
|
||||
`fix:`/`perf:` — patch, `!` или `BREAKING CHANGE:` — major, в том числе до 1.0.
|
||||
`docs:`, `spec:`, `chore:`, `build:`, `ci:`, `refactor:`, `test:`, `style:` и
|
||||
`revert:` дают patch при выпуске. `chore(release):` — служебная запись,
|
||||
исключённая из changelog и рекомендации версии. Release-it не запускается
|
||||
автоматически после каждого commit. Номер связанной issue можно добавить в заголовок.
|
||||
автоматически после каждого commit. Ссылка на Issue — в
|
||||
[Commit Conventions](.ai/conventions/engineering-workflow.md#commit-conventions).
|
||||
|
||||
### Подготовка релиза
|
||||
|
||||
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
|
||||
Проверить, что текущий `develop` содержит последний релиз из `main`.
|
||||
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
|
||||
Проверить инвариант. Создать `release/X.Y.Z` от проверенного `develop`
|
||||
и выполнить `npm ci`.
|
||||
2. Проверить локальные входы и публичные artifacts выпускаемого репозитория.
|
||||
Совместимость развёрнутых участников согласовать отдельно через публичные
|
||||
контракты; CI не скачивает соседние checkout и не требует их SHA.
|
||||
@@ -184,19 +202,20 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
||||
наличие коммитов. Теги и соответствие версии последнему принятому тегу
|
||||
проверяются вручную; уже подготовленную версию повторно не повышать.
|
||||
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
|
||||
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
|
||||
ветку: `git push -u origin HEAD`. Открыть PR в **`develop`** с заголовком
|
||||
`chore(release): X.Y.Z`. После зелёных обязательных проверок (`commits` в root;
|
||||
`verify`, `security`, `commits` в остальных repo)
|
||||
выполнить **fast-forward-only merge**. Если `main` изменился, сначала
|
||||
согласовать историю и повторить проверку кандидата; не обходить защиту.
|
||||
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
|
||||
коммит, версия и lockfile совпадают. На этом SHA создать аннотированный тег:
|
||||
выполнить **fast-forward-only merge**. Если `develop` изменился, обновить
|
||||
релизную ветку и повторить проверку кандидата; не обходить защиту.
|
||||
5. Открыть PR `develop → main` с тем же заголовком и слить
|
||||
**fast-forward-only**. `main` получает ровно проверенный релизный коммит;
|
||||
merge-коммит здесь невозможен по определению ff.
|
||||
6. Получить `main` и убедиться, что его HEAD — проверенный релизный коммит,
|
||||
версия и lockfile совпадают. На этом SHA создать аннотированный тег:
|
||||
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправить
|
||||
только этот тег: `git push origin refs/tags/vX.Y.Z`. Повторно release-it
|
||||
на `main` не запускать: он уже подготовил версию.
|
||||
6. Через PR `main → develop` вернуть релизный коммит в разработку без squash
|
||||
и rebase. Убедиться, что `develop` содержит тег. После первого выпуска
|
||||
`develop` создаётся непосредственно от релизного SHA.
|
||||
на `main` не запускать: он уже подготовил версию. Обратная синхронизация
|
||||
не нужна: релизный коммит попал в `develop` на шаге 4.
|
||||
|
||||
Перед публикацией тегов сравнить local SHA с удалённым `main`; PR и проверки
|
||||
должны относиться к этому кандидату. Опубликованные теги не перемещать.
|
||||
@@ -210,11 +229,20 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
||||
Исторические неконвенциональные commits не переписываются; первый changelog
|
||||
требует просмотра человеком. Дальнейшие версии выбираются по commits.
|
||||
|
||||
### Hotfix, проверки и защита
|
||||
### Ограничения модели
|
||||
|
||||
Срочный fix начинается от `main`, проходит проверки и релизный PR из
|
||||
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
|
||||
Если ломается публичный контракт, major сохраняется и для hotfix.
|
||||
Freeze релиза не поддерживается: кандидат нельзя заморозить и параллельно
|
||||
принимать работу в `develop`. Пока открыт релизный PR, `develop` не двигают.
|
||||
|
||||
Срочный fix — обычная ветка `fix/<issue>-<slug>` от `develop` с последующим
|
||||
релизом. Отдельного пути от `main` нет: он сделал бы `main` не-предком
|
||||
`develop` и заблокировал следующий ff. Если `develop` содержит незаконченную
|
||||
работу, быстрого фикса нет — это цена отказа от release-ветки в `main`.
|
||||
Исключение требует явного решения: `release/hotfix-<name>` от `main`,
|
||||
PR в `main`, тег, затем merge `main → develop` для восстановления инварианта.
|
||||
Такой релиз оставляет merge-коммит в `develop`; cherry-pick из `develop`
|
||||
в hotfix-ветку порождает дубль коммита при последующем rebase и не
|
||||
применяется. Если ломается публичный контракт, major сохраняется и для hotfix.
|
||||
|
||||
Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера,
|
||||
сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному
|
||||
@@ -225,12 +253,17 @@ snapshot. Docs проверяет внутренние ссылки; внешн
|
||||
доступ к root для выпуска другого репозитория не требуется.
|
||||
|
||||
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
|
||||
обязательные проверки: в root только `commits`, в остальных repo —
|
||||
`verify`, `security`, `commits`; также нужен запрет обхода правил
|
||||
администратором. Требование отдельного одобрения review не вводится для
|
||||
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
|
||||
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
|
||||
релиза; их удаление или перенос не входит в обычный процесс.
|
||||
блокировка устаревшей ветки перед merge, обязательные проверки: в root только
|
||||
`commits`, в остальных repo — `verify`, `security`, `commits`; также нужен
|
||||
запрет обхода правил администратором. Требование отдельного одобрения review
|
||||
не вводится для разработки одним владельцем. Ветка по умолчанию — `develop`;
|
||||
Renovate тоже отправляет PR в `develop`. Теги `v*` защищены, создание
|
||||
разрешается владельцу релиза; их удаление или перенос не входит в обычный процесс.
|
||||
|
||||
Линейность истории Gitea отдельной опцией не выражает, её обеспечивают
|
||||
разрешённые merge styles репозитория: merge-коммиты и rebase с `--no-ff`
|
||||
выключены, доступны squash, rebase-merge и fast-forward-only. Стиль по
|
||||
умолчанию — squash, обновление ветки — rebase, ветка после merge удаляется.
|
||||
|
||||
В root нет собственного test/verify runner и release hooks. Release-it и
|
||||
commitlint используются напрямую. Остальные репозитории сохраняют свои gates.
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Technical Audit Remediation — 2026-10-11
|
||||
|
||||
Исторический аудит WealthLedger от 11 октября 2026 года: root coordination,
|
||||
frontend, backend, specs и docs. Программа управляется Gitea Issues; этот
|
||||
архив сохраняет доказательства и решения triage, а не текущие статусы задач.
|
||||
|
||||
Главный Issue: [Technical Audit Remediation — WealthLedger — 2026-10-11](https://git.personal.ksv741.ru/WealthLedger/main/issues/23).
|
||||
Нормализованный реестр и задачи: [triage](triage.md).
|
||||
|
||||
## Исследованные revisions
|
||||
|
||||
HEAD повторно проверены при remediation planning; application checkout чистые.
|
||||
Все пять исходных commits существуют в соответствующих remote repositories.
|
||||
|
||||
| Repository | Verified revision |
|
||||
| --- | --- |
|
||||
| [WealthLedger/main](https://git.personal.ksv741.ru/WealthLedger/main) | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` |
|
||||
| [WealthLedger/frontend](https://git.personal.ksv741.ru/WealthLedger/frontend) | `c8821200ce699dab1ce65360d1968d5877a2e78b` |
|
||||
| [WealthLedger/backend](https://git.personal.ksv741.ru/WealthLedger/backend) | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` |
|
||||
| [WealthLedger/specs](https://git.personal.ksv741.ru/WealthLedger/specs) | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` |
|
||||
| [WealthLedger/docs](https://git.personal.ksv741.ru/WealthLedger/docs) | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` |
|
||||
|
||||
## Отчёты
|
||||
|
||||
Все 18 источников прочитаны. Специализированные отчёты — основной источник;
|
||||
предыдущие отчёты дают дополнительные свидетельства и гипотезы. Повторение
|
||||
вывода в прежнем отчёте не считается независимым подтверждением.
|
||||
|
||||
| № | Источник / архив |
|
||||
| --- | --- |
|
||||
| 01 | [Architecture & Module Boundaries](reports/01-architecture-and-module-boundaries-report.md) |
|
||||
| 02 | [Complexity & Overengineering](reports/02-complexity-and-overengineering-report.md) |
|
||||
| 03 | [Duplication & Reusability](reports/03-duplication-and-reusability-report.md) |
|
||||
| 04 | [Dead Code & Legacy](reports/04-dead-code-and-legacy-report.md) |
|
||||
| 05 | [Frontend Architecture](reports/05-frontend-architecture-report.md) |
|
||||
| 06 | [Backend Architecture](reports/06-backend-architecture-report.md) |
|
||||
| 07 | [API & Data Flow Efficiency](reports/07-api-and-data-flow-efficiency-report.md) |
|
||||
| 08 | [Test Quality & Effectiveness](reports/08-test-quality-and-effectiveness-report.md) |
|
||||
| 09 | [Test Architecture & Infrastructure](reports/09-test-architecture-and-infrastructure-report.md) |
|
||||
| 10 | [Custom Scripts & Tooling](reports/10-custom-scripts-and-tooling.md) |
|
||||
| 11 | [Validation & CI/CD Efficiency](reports/11-validation-and-ci-cd-efficiency-report.md) |
|
||||
| 12 | [Dependencies & Configuration](reports/12-dependencies-and-configuration-report.md) |
|
||||
| 13 | [Reliability & Security](reports/13-reliability-and-security-report.md) |
|
||||
| 14 | [AI Agent Workflow Efficiency](reports/14-ai-agent-workflow-efficiency.md) |
|
||||
| 15 | [SDD, Specs & Documentation](reports/15-sdd-specs-and-documentation.md) |
|
||||
| 16 | [complexity-audit.md](reports/previous/complexity-audit.md) |
|
||||
| 17 | [technical-audit-2026-10-11.md](reports/previous/technical-audit-2026-10-11.md) |
|
||||
| 18 | `audit-results/АудитЭффективности.md` — publication blocked, см. ниже |
|
||||
|
||||
Фактическое имя последнего источника — `АудитЭффективности.md`, а не
|
||||
`АудитЭффективности(1).md`. Соответствие установлено по содержимому.
|
||||
|
||||
## Ключевые выводы
|
||||
|
||||
- P1: hourly lifecycle способен сохранить ложный успех или зависнуть после
|
||||
persistence failure; overlapping forecast fallback блокирует новый complete
|
||||
candidate. Runtime-воспроизведение этих дефектов в planning не выполнялось.
|
||||
- Переход к hourly/local/online ownership неполон: calendars, Income/Ledger,
|
||||
query timer и UI feedback сохраняют несовместимые части прежнего поведения.
|
||||
- Тесты требуют различающих stimuli и completion barriers; настоящая SQLite,
|
||||
financial precision, auth CAS, replay и publication fences сохраняются.
|
||||
- Дорогая повторная preparation сокращается в существующих workflow; новые
|
||||
слои, shared tooling, validators и постоянная audit automation не нужны.
|
||||
- Specs/current docs требуют точечной reconciliation. Исторические ADR,
|
||||
OpenSpec archives и стандартные lifecycle skills остаются неизменяемыми.
|
||||
|
||||
## Сохранность и публикация
|
||||
|
||||
17 опубликованных отчётов копируются byte-for-byte из `audit-results/`;
|
||||
содержание, исходные имена и исторические утверждения не исправляются.
|
||||
Внутренние workspace-relative ссылки оригиналов сохранены как evidence;
|
||||
актуальные remote permalinks и навигация принадлежат README/triage/Issues.
|
||||
Оригинальные 18 локальных файлов не удаляются. SHA manifests и новые
|
||||
integrity validators не вводятся: версии архива принадлежат Git.
|
||||
|
||||
`gitleaks dir audit-results --redact=100 --no-banner --log-level error`
|
||||
завершился с exit 0. Дополнительно выполнен содержательный privacy review.
|
||||
Все пять remote repositories публичные. Отчёт `АудитЭффективности.md`
|
||||
содержит идентификаторы и названия частных OpenCode sessions: неизменённый
|
||||
оригинал нельзя безопасно публиковать здесь. Он сохранён в исходном локальном
|
||||
месте; все RC-01–RC-18 рассмотрены в triage без частных session identifiers.
|
||||
Блокер снимается предоставлением подходящего приватного Git owner/location
|
||||
либо явным решением владельца о допустимости публикации этих metadata.
|
||||
Редактирование оригинала или публикация приватных conversations не предлагаются.
|
||||
|
||||
Архив публикуется в task branch `codex/audit-remediation-2026-10-11` от свежего
|
||||
`origin/develop`. Локальный commit без подтверждённого push не считается
|
||||
публикацией. Release/version bump из названия программы не следует.
|
||||
|
||||
Первый архивный commit `0d6344abfdf9c4c6139e4d2b50bf3bd3b2ee690e` успешно
|
||||
отправлен и прочитан через Gitea; отчёты в нём неизменяемы. Финальные README и
|
||||
triage доступны в той же remote task branch и имеют фактические Issue URLs.
|
||||
|
||||
## Gitea программа
|
||||
|
||||
39 нормализованных engineering topics поставляются 43 working Issues:
|
||||
40 новых и 3 повторно используемых. Главный navigation Issue создан отдельно;
|
||||
итого 41 новое Issue и 44 participating Issues. Все новые задачи неназначены;
|
||||
существующие assignees/тексты сохранены, audit supplements добавлены comments.
|
||||
|
||||
| Repository | Milestone | Participating Issues |
|
||||
| --- | --- | --- |
|
||||
| main | [milestone 1](https://git.personal.ksv741.ru/WealthLedger/main/milestone/1) | 3, включая главный Issue |
|
||||
| backend | [milestone 4](https://git.personal.ksv741.ru/WealthLedger/backend/milestone/4) | 23 |
|
||||
| frontend | [milestone 3](https://git.personal.ksv741.ru/WealthLedger/frontend/milestone/3) | 11 |
|
||||
| specs | [milestone 5](https://git.personal.ksv741.ru/WealthLedger/specs/milestone/5) | 5 |
|
||||
| docs | [milestone 2](https://git.personal.ksv741.ru/WealthLedger/docs/milestone/2) | 2 |
|
||||
|
||||
Milestones не имеют due date. Переиспользованы organization labels;
|
||||
8 новых repo-scoped labels — 5 audit и 3 investigation. Создание org labels
|
||||
отвергнуто недостатком organization-owner permission; repo-scoped fallback
|
||||
подтверждён. Для reused Issues назначение labels сделано через replace_labels:
|
||||
обычный issue update в MCP их не применяет. Оба ограничения API обработаны.
|
||||
Native dependencies установлены и прочитаны обратно, cycles отсутствуют.
|
||||
@@ -0,0 +1,820 @@
|
||||
# Аудит 01 — Architecture & Module Boundaries
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID/name:** 01 / Architecture & Module Boundaries.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Исполнитель:** один независимый исполнитель направления 01. Другие
|
||||
субагенты не запускались, новые отчёты других направлений не читались.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
- **Режим:** исследование read-only; единственная запись — этот отчёт вне
|
||||
четырёх самостоятельных checkout.
|
||||
|
||||
| Checkout | Абсолютный путь | Проверенный HEAD | Начальный status | Итоговый status после записи отчёта |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean | clean |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean | clean |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean | clean |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean | clean |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean | clean |
|
||||
|
||||
SHA/status получены самостоятельно командами `git rev-parse HEAD` и
|
||||
`git status --short --untracked-files=all` из каждого владельца. Пустой вывод
|
||||
status означает отсутствие staged, unstaged и видимых untracked изменений;
|
||||
ignored файлы этим не инвентаризируются.
|
||||
|
||||
**Инструкции и skills.** Первым загружен установленный `ponytail-audit`.
|
||||
Применён его complexity-only проход: поиск недоступной orchestration,
|
||||
лишней indirection и механизма, заменяемого возможностью установленного stack.
|
||||
Correctness-сигналы отделены от этого прохода. Полная структура отчёта следует
|
||||
протоколу `audit-prompts/01-architecture-and-module-boundaries.md`, прочитанному
|
||||
целиком. Прочитаны AGENTS каждого checkout, местные architecture/engineering
|
||||
и активированные data, auth/security, integrations, HTTP/errors, testing,
|
||||
frontend TypeScript/React и UI conventions. Root conventions использованы
|
||||
только для root и сохранения отчёта. Дополнительно применены
|
||||
`feature-sliced-design` с reference `layer-structure.md` и
|
||||
`verification-before-completion`. Локальный FSD profile не заменён общими
|
||||
рекомендациями skill.
|
||||
|
||||
**Graphify limitation.** Skill загружен, но существующий
|
||||
`graphify-out/graph.json` отсутствует в root, frontend, backend, specs и docs
|
||||
по проверке известных абсолютных путей. Query существующего графа невозможен.
|
||||
Графы и дополнительные artifacts не генерировались; выполнена статическая
|
||||
трассировка source/imports/DI. Это не отказ от аудита из-за отсутствия графа.
|
||||
|
||||
**Выполненные проверки:**
|
||||
|
||||
1. Git HEAD/status в пяти checkout в начале, перед сохранением и после
|
||||
сохранения; SHA совпадают, source/config trees чистые.
|
||||
`git diff --check` в каждом checkout — без ошибок. Сам ignored отчёт
|
||||
отдельно проверен `git diff --no-index --check /dev/null audit-results/01-architecture-and-module-boundaries-report.md` — без whitespace errors.
|
||||
2. Чтение manifests, module registrations, public exports, controllers,
|
||||
application owners, relevant persistence/schema, query consumers и
|
||||
контраргументов; поиск через Read/Glob/Grep из соответствующих repo paths.
|
||||
3. В frontend, после активации местной Node:
|
||||
`source "$HOME/.nvm/nvm.sh" && nvm use --silent && ./node_modules/.bin/depcruise src packages --config dependency-cruiser.cjs --output-type err-long`.
|
||||
Exit 0: **420 modules, 1773 dependencies, no dependency violations**.
|
||||
Это прямой запуск установленного анализатора; `npm run deps` не запускался,
|
||||
потому что его pre-step собирает packages
|
||||
(`frontend/package.json:24-26,33`).
|
||||
4. Из root:
|
||||
`git diff --no-index --stat "backend/openapi.json" "frontend/src/shared/api/spec/openapi.json"`.
|
||||
Exit 0, различий файлов на диске нет. Это не свежая генерация и не
|
||||
доказательство runtime schema parity.
|
||||
5. Context7: документация TanStack Query `/tanstack/query` по polling,
|
||||
`refetchInterval` и отличию automatic fetching от manual `refetch`.
|
||||
Справочный запрос не обращался к прикладным финансовым API.
|
||||
|
||||
**Уточнение scope:** dead-code и documentation/index вопросы перенесены в
|
||||
короткие Cross-audit signals для направлений 04 и 15. При корректировке
|
||||
перечитан и изменён только этот отчёт; проверки source graph и OpenAPI из
|
||||
первичного аудита повторно не запускались. Counts, related IDs и таблицы
|
||||
относятся к оставшимся архитектурным findings.
|
||||
|
||||
**Не запускались:** install, build, codegen/OpenAPI generation, migrations,
|
||||
application startup, full-suite/verify, SQLite/E2E/unit tests, нагрузочные
|
||||
проверки, commit/PR и реальные внешние API. `references/**`, dated audits,
|
||||
архивы и исходники не изменялись. Предыдущий
|
||||
`audit-results/technical-audit-2026-10-11.md` прочитан только как реестр
|
||||
гипотез; его результаты измерений не использованы вместо текущих проверок.
|
||||
|
||||
Все `path:line` ниже отсчитываются от workspace root и относятся к указанным
|
||||
HEAD. Ссылка на тест означает статически просмотренный сценарий либо будущую
|
||||
проверку, но не результат запуска теста в этом аудите.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт:** модульный NestJS monolith и локальный frontend FSD profile
|
||||
жизнеспособны. Прослеженные Nest module imports не образуют цикла;
|
||||
frontend dependency-cruiser не обнаружил циклов/forbidden imports по
|
||||
текущим правилам. Не доказана необходимость CQRS, обязательных ports,
|
||||
дополнительного state store или микросервисов.
|
||||
2. **Подтверждённый boundary risk / ARCH-002:** read orchestration операций
|
||||
шире разрешённой первой загрузки. Income GET инициирует membership work,
|
||||
а общий `readPlan` с `history: true` может загрузить отсутствующую историю
|
||||
вне запрошенного периода. Это конкурирующая refresh policy рядом с hourly
|
||||
owner, а не недостаток слоёв.
|
||||
3. **Подтверждённый ownership drift / ARCH-003:** периодический GET принадлежит
|
||||
одновременно query owner, глобальному QueryClient и timer в feature
|
||||
подключения. Разрешённый import `pages → features` не делает этот owner
|
||||
осмысленным; timer не знает query eligibility.
|
||||
4. **KEEP / ARCH-005–007:** auth coordination, account/connection fences,
|
||||
общая conditional publication и banking transaction/replay boundaries
|
||||
имеют конкретных production consumers. Их удаление не является
|
||||
допустимым сокращением архитектуры.
|
||||
5. **UNVERIFIED / ARCH-008–009:** перенос event-specific semantics из generic
|
||||
forecast и удаление persisted `observe` reference-path требуют проверки
|
||||
product semantics/совместимости. Наличие нескольких consumers и публичного
|
||||
параметра — реальные контраргументы против механического удаления.
|
||||
|
||||
**Подсчёт:** actionable = `DELETE`, `SIMPLIFY`, `FIX` в разделе 4.
|
||||
**P0: 0; P1: 0; P2: 2; P3: 0. KEEP: 3; INVESTIGATE: 2.**
|
||||
Severity не превращена в порядок cleanup. Для KEEP использован P3, поскольку
|
||||
текущий дефект не заявляется; важность защищаемых инвариантов описана отдельно.
|
||||
Для INVESTIGATE severity — уровень рассмотрения гипотезы, не подтверждение
|
||||
сбоя. Runtime/performance impact не измерялся.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Фактический layout и слои
|
||||
|
||||
**Backend.** Manifest объявляет NestJS 12, TypeScript 6, Prisma 7 и
|
||||
better-sqlite3; schema задаёт SQLite, а не Postgres
|
||||
(`backend/package.json:40-66,104-105`, `backend/prisma/schema.prisma:1-9`).
|
||||
Прочитаны регистрации всех найденных `src/**/*.module.ts`:
|
||||
composition root, Auth, BankCards, BrokerageAccounts, Portfolio, Operations,
|
||||
Income, UpcomingEvents, Instruments, InstrumentSources, TbankConnection,
|
||||
TbankSync, TbankSyncRepository, Tbank/Moex/Ncr, Prisma и Logging.
|
||||
Это DI inventory, а не предположение по названиям директорий.
|
||||
|
||||
```text
|
||||
AppModule
|
||||
├─ Auth / BankCards
|
||||
├─ BrokerageAccounts → Portfolio
|
||||
├─ Income → Operations
|
||||
├─ Instruments → InstrumentSources + Portfolio
|
||||
├─ UpcomingEvents → InstrumentSources + Portfolio
|
||||
├─ TbankConnection → TbankSync
|
||||
└─ TbankSync → Portfolio + Operations + UpcomingEvents + InstrumentSources
|
||||
|
||||
Personal source owners → TbankSyncRepositoryModule (нижний durable store)
|
||||
Business/source owners → TbankModule → gRPC client / access / membership
|
||||
InstrumentSources → MoexModule + NcrModule
|
||||
Global infrastructure: ConfigModule, PrismaModule, LoggingModule
|
||||
```
|
||||
|
||||
Контекст legacy DI: зарегистрированный status service содержит также
|
||||
screen-command orchestration и зависимости source owners
|
||||
(`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:42-52,54-311`),
|
||||
но HTTP controller вызывает только status
|
||||
(`backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31-40`).
|
||||
Это часть фактической dependency map; решение о dead-code closure принадлежит
|
||||
направлению 04.
|
||||
|
||||
Intended ownership сравнивался с принятым ADR 019: hourly walk обновляет
|
||||
personal sources, обычные GET локальны, Ledger/candles имеют узкие first-load
|
||||
исключения (`docs/adr/019-hourly-reads-and-online-instrument-reference.md:9-29`).
|
||||
Current ownership показан production traces ниже; расхождения read initiators
|
||||
оценены в ARCH-002. Актуальность docs/index рассматривается направлением 15.
|
||||
|
||||
Важный контрпример предполагаемому циклу:
|
||||
`TbankSyncModule → PortfolioModule → TbankSyncRepositoryModule` не возвращается
|
||||
в `TbankSyncModule`. Store module регистрирует только repository
|
||||
(`backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`).
|
||||
Также `investment-sync-plan → income.api` достигает только pure
|
||||
`planIncomePeriod`, не `IncomeAnalyticsService`
|
||||
(`backend/src/modules/investments/income/income.api.ts:1`).
|
||||
Source import cycle, Nest module cycle и runtime DI cycle не отождествляются.
|
||||
Полный backend AST/DI graph checker не запускался; отсутствие всех возможных
|
||||
транзитивных source cycles не заявляется.
|
||||
|
||||
**Frontend.** Manifest задаёт React/Vite/TypeScript, TanStack Query,
|
||||
openapi-fetch/openapi-react-query и private local packages
|
||||
(`frontend/package.json:39-62,112-116,128-130`). Реальное направление:
|
||||
|
||||
```text
|
||||
app/providers/routes → pages/{account,accounts,income,events,operations,...}
|
||||
pages → features/{auth,tbank-connection,instrument-search,instrument-details}
|
||||
pages → entities/{portfolio,income,broker-operation,brokerage-event,bond,...}
|
||||
pages → widgets/portfolio-structure
|
||||
features/entities → shared/{api,lib,i18n,ui}
|
||||
src consumers → packages/ui,icons; packages/ui → packages/tokens
|
||||
```
|
||||
|
||||
Routes lazy-import page public APIs
|
||||
(`frontend/src/app/routes/AppRoutes/AppRoutes.tsx:9-103`), account VM собирает
|
||||
entity queries через публичные index
|
||||
(`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:6-18,66-84`).
|
||||
Query options принадлежат entities; shared/api владеет transport, session-aware
|
||||
eligibility и resource projection. Пример:
|
||||
`frontend/src/entities/portfolio/api/usePortfolio.ts:11-25` →
|
||||
`frontend/src/shared/api/resource-query.ts:85-149` →
|
||||
`frontend/src/shared/api/authenticated-query.ts:34-50` →
|
||||
`frontend/src/shared/api/client.ts:53-119,284-290`.
|
||||
|
||||
Размеры 420/1773 получены из единственного текущего запуска depcruise на
|
||||
`src packages`, с `doNotFollow: node_modules`, TS pre-compilation dependencies,
|
||||
source aliases и исключением package dist
|
||||
(`frontend/dependency-cruiser.cjs:78-87`,
|
||||
`frontend/tsconfig.dependency-cruiser.json:1-12`). Они включают объекты графа,
|
||||
в том числе test/story source, и не означают 420 runtime business modules.
|
||||
Правила проверяют imports, public APIs, cycles и direction
|
||||
(`frontend/dependency-cruiser.cjs:1-76`), но не ownership refresh policy.
|
||||
|
||||
### 3.2. Сквозные production traces
|
||||
|
||||
| Capability | Controller/query → application → rules → storage/integration | Проверенная boundary |
|
||||
| --- | --- | --- |
|
||||
| Auth/session | App/AuthProvider → auth VM → shared authenticatedFetch; AuthController → AuthService → Session/JWT | В браузере один session owner и callback-based transport; cookie пишет controller; rotation CAS остаётся у AuthService. Evidence: `frontend/src/features/auth/model/auth.vm.ts:29-151`; `backend/src/modules/auth/presentation/auth.controller.ts:81-118`; `backend/src/modules/auth/application/auth.service.ts:81-114,166-201` |
|
||||
| Accounts/portfolio | Account VM → usePortfolio → PortfolioController → PortfolioService.read/saved → PortfolioRepository; отдельно hourly → PortfolioService.request → gRPC → mapper → publish | HTTP read локален; production producer и publisher сохранены отдельно от read composition. Evidence: `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:66-84`; `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:19-32`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:55-59,113-232,234-284` |
|
||||
| Instrument discovery | BondsPage VM → useBondSearch → BondCatalogService → CatalogDiscoveryService.onlineSearch → TbankClient; source calendars отдельно через CatalogEnrichmentService | Default reference read online; persisted observe branch остаётся доступен. Evidence: `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9-13`; `frontend/src/entities/bond/api/queries.ts:97-104`; `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25-55`; `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84-94,295-337` |
|
||||
| Sync/forecast | Native tick → reserveHourly → membership → portfolio → operations → events.refresh/get → ForecastService → ForecastRepository | Coordinator задаёт порядок, owners выполняют sources. Event GET также рассчитывает candidate и проходит write-capable resolve. Evidence: `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48-65,76-110,127-175`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:54-173`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:24-46,170-175` |
|
||||
| Banking operations | Frontend bank mutation → BankCardsController → BankOperationsService → operationInput/validateOperation → BankCardsRepository.write → SQLite; summary → GetBankSummaryUseCase → pure bankSummary | Banking не зависит от broker integration; transaction охватывает idempotency, refund validation, versioned write и response read. Evidence: `frontend/src/entities/bank-card/api/mutations.ts:29-82`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:127-166`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240`; `backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.ts:12-35` |
|
||||
|
||||
### 3.3. Ownership map — специальный результат
|
||||
|
||||
Пять центральных capabilities ниже разделены по решениям/состояниям/записям,
|
||||
чтобы несколько технических participants не выдавать за нескольких owners.
|
||||
|
||||
| Capability / решение и состояние | Current owner | Competing owner | Recommended owner | Evidence |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Auth/session: локальная identity, refresh hash, rotation | Backend AuthService; browser auth VM — только клиентская session | Конкурирующий session writer в прослеженных путях не найден; shared client получает callbacks | Сохранить это разделение; shared client не становится auth domain owner | `backend/src/modules/auth/application/auth.service.ts:81-114,166-201`; `frontend/src/features/auth/model/auth.vm.ts:29-115,143-151` |
|
||||
| Accounts/portfolio: создание/replacement connection и начального membership | TbankConnectionService.put, одна транзакция новой revision | TbankAccessService.publishMembership — последующее подтверждение текущей revision, а не competing replacement | Lifecycle подключения остаётся в connection module; последующие membership writes у существующего access owner | `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`; `backend/src/integrations/tbank/tbank-access.service.ts:148-237` |
|
||||
| Accounts/portfolio: право видеть счёт и membership freshness | TbankAccessService.local; source producer TbankMembershipService; refresh cadence у hourly | Income collectRanges инициирует refresh membership | Hourly инициирует повторное membership чтение; personal GET использует только local boundary | `backend/src/integrations/tbank/tbank-access.service.ts:260-288`; `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:113-125`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:428-435` |
|
||||
| Accounts/portfolio: positions, точные totals, сохранённый portfolio | PortfolioService/PortfolioRepository; AccountsService только строит summary | Legacy screen dispatcher содержит portfolio branch без найденного production entrypoint; это DI-контекст, не текущий refresh initiator | Portfolio source owner сохраняется; инициирование — hourly, projection — существующие readers | `backend/src/modules/investments/portfolio/application/portfolio.service.ts:179-198,234-284`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116-184`; `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts:16-39`; `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:99-115` |
|
||||
| Instrument discovery: поиск/identity и публичная projection | CatalogDiscoveryService + stock/bond catalog services | Default online и persisted observe представляют разные поколения read policy | Default online owner сохранить; удаление persisted compatibility пока INVESTIGATE | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84-167,220-292`; `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25-88` |
|
||||
| Instrument discovery: сохранённые calendars/quotes/candles | CatalogEnrichmentService/CatalogDiscoveryRepository и CandleService/CandleRepository | Metadata lookup хранит другую identity projection; само по себе не competing calendar owner | Сохранить source-specific owners, не сливать financial sources с online reference answers | `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:100-160,164-230`; `backend/src/modules/investments/instruments/application/candle.service.ts:101-124`; `backend/prisma/schema.prisma:148-229` |
|
||||
| Sync/forecast: момент запуска личной работы и durable attempt | TbankHourlyScheduler + TbankSyncRepository; Ledger — узкое first-load исключение | Income scheduling и readPlan history expansion — реальные read initiators; TbankSyncService.request/dispatch — legacy DI-контекст | Hourly — единственный repeat-refresh initiator; Ledger запускает только missing requested interval | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:76-175`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:92-124`; `specs/openspec/specs/tbank-data-sync/spec.md:9-22` |
|
||||
| Sync/forecast: window, event amounts, filtering и complete publication | UpcomingEventsService рассчитывает события; ForecastService фильтрует events; ForecastRepository выбирает overlapping window и публикует | Event semantics распределена между capability и generic portfolio forecast owner; generic publication имеет ещё stock/bond consumers | Сохранить один общий fenced publisher; event window/filter policy предпочтительно у Events, но перенос после semantic proof | `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:119-173,271-299`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:98-123`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79,179-186` |
|
||||
| Banking operations: intent, refund eligibility, journal write, version | BankOperationsService внутри BankCards module; repository владеет общей DB transaction boundary | UI хранит draft/intent, но не authority для refund limit; competing server writer не найден | Сохранить cohesive transactional owner и pure money rules; не дробить transaction по картам/категориям/возвратам | `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240,251-330`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35-66`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12-38` |
|
||||
|
||||
### 3.4. Dependency/Boundary matrix — специальный результат
|
||||
|
||||
| From → To | Фактическая зависимость и consumer | Сопоставление с intended boundary / оценка |
|
||||
| --- | --- | --- |
|
||||
| Frontend app → pages/features | Lazy routes и AuthProvider: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:9-103`; `frontend/src/app/ui/App.tsx:10-37` | Корректная верхняя composition root |
|
||||
| Frontend pages → entities/features | Account VM queries и screen timer: `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:6-18,66-84` | Public imports соблюдены; semantic owner timer требует ARCH-003 |
|
||||
| Frontend entities → shared/api | usePortfolio/useBondSearch: `frontend/src/entities/portfolio/api/usePortfolio.ts:1-25`; `frontend/src/entities/bond/api/queries.ts:97-104` | Transport общий, endpoint identity/policy локальны; сохранить |
|
||||
| Frontend features/auth → shared transport | Callback registration: `frontend/src/features/auth/model/auth.vm.ts:143-151`; `frontend/src/shared/api/client.ts:28-35,122-125` | Обратный callback не является forbidden source import shared → feature |
|
||||
| Frontend src → private packages | App theme и exports: `frontend/src/app/ui/App.tsx:7,25-28`; `frontend/packages/ui/package.json:9-19,26-33` | Реальные boundaries для app/catalog consumers; не дополнительные runtime services |
|
||||
| Frontend packages → src | Запрет `frontend/dependency-cruiser.cjs:18-23` | В текущем source graph нарушений не найдено |
|
||||
| Backend HTTP → application | Portfolio/Income/Bank controllers: `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:17-31`; `backend/src/modules/investments/income/income-analytics.controller.ts:24-79`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:127-166` | Controller делегирует, identity из verified user; сохраняется |
|
||||
| Backend Income → Operations | `backend/src/modules/investments/income/income.module.ts:3-12`; `backend/src/modules/investments/income/income-analytics.service.ts:45-93` | Аналитика должна читать ledger data; скрытая scheduling branch — ARCH-002 |
|
||||
| Backend UpcomingEvents/Instruments → Portfolio forecast | `backend/src/modules/investments/upcoming-events/upcoming-events.module.ts:5-15`; `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:85-94`; `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:116-125` | Общее publication/coherence имеет несколько consumers; event-specific window protocol — ARCH-008 |
|
||||
| Backend TbankSync → resource owners | `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:14-25`; scheduler `:113-175` | Направление coordinator → owners обосновано; broad legacy DI описана как контекст inventory |
|
||||
| Backend resource owners → sync store | Portfolio/Operations/Events imports: `backend/src/modules/investments/portfolio/application/portfolio.service.ts:20-23`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:20-23`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:23-26` | Store module экспортирует repository и не импортирует coordinator. Цикл не доказан. Прямой файл вместо `*.api.ts` отступает от общего documented import profile, но это реально экспортированный provider, не доказательство плохого owner |
|
||||
| Backend business → Tbank integration | `backend/src/integrations/tbank/tbank.module.ts:10-25`; `backend/src/modules/investments/portfolio/portfolio.module.ts:3-21` | Client/cache/limiter регистрируются у одного integration owner |
|
||||
| Backend integration → business modules | Просмотр imports в integrations и `backend/src/integrations/tbank/tbank-access.service.ts:9-15` | В просмотренных import paths обратного обращения к business module нет; Prisma access — documented technical persistence, не HTTP/domain dependency |
|
||||
| Backend BankCards → Auth/Prisma/domain | `backend/src/modules/banking/bank-cards/bank-cards.module.ts:3-19`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:3-29` | Cohesive CRUD service использует tx client по локальному profile; обязательный repository wrapper на каждый query не нужен |
|
||||
| Backend domain → framework/storage | ESLint guard: `backend/eslint.config.mjs:126-168`; pure RUB rules `backend/src/modules/banking/bank-cards/domain/rub-money.ts:1-38` | Прямые imports ограничены; эта проверка не доказывает транзитивный граф или DI. Не выдаётся новый blanket lint verdict |
|
||||
| Backend → frontend contract | Nest metadata → предоставленный JSON → generated frontend types: `backend/scripts/generate-openapi.cjs:19-35`; `frontend/scripts/check-openapi.mjs:10-26` | Передаётся artifact, не shared TypeScript source соседнего checkout. Файлы snapshot на диске совпадают; fresh parity UNVERIFIED |
|
||||
| Specs/docs → application | Specs behavioral input; docs объясняют architecture: `specs/AI_INDEX.md:3-13`; `docs/README.md:52-67` | Не runtime/source dependency; intended policy сопоставлена с production ownership, documentation/index maintenance относится к audit 15 |
|
||||
|
||||
Обнаруженные прямые imports экспортированных backend providers не
|
||||
приравниваются к доказанным DI/circular failures. Например,
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:10-13`
|
||||
читает integration implementation entry вместо `tbank.api.ts`; provider
|
||||
зарегистрирован в TbankModule, альтернативная implementation не заявлена.
|
||||
Смысл boundaries проверен по consumers, а не по наличию barrel-файла.
|
||||
|
||||
### 3.5. SRP, cohesion, DIP/ISP, OCP/LSP
|
||||
|
||||
- **SRP/cohesion:** Auth session, connection lifecycle, source membership,
|
||||
portfolio publication и banking journal имеют разные причины изменения
|
||||
и реальные write boundaries. BankCardsService, BankOperationsService,
|
||||
ExpenseCategoriesService и summary use case в одном module не требуют
|
||||
отдельных Nest modules. Legacy screen dispatcher соединяет те же source
|
||||
owners на уровне DI; dead-code решение вынесено в cross-audit signal 04.
|
||||
- **Coupling:** Income зависит от read service, который также инициирует
|
||||
membership/full-history work; фронтенд-страницы зависят от connection feature
|
||||
ради периодических resource reads. Это проверенные связи, не вывод по имени
|
||||
класса. Forecast window меняется сразу в Events и generic portfolio
|
||||
persistence/service — ARCH-008.
|
||||
- **DIP/ISP:** Concrete clients/repositories допустимы и имеют test seams.
|
||||
`TbankConnectionService` использует `Pick<TbankClient, 'validateToken'>`
|
||||
(`backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:11-19`),
|
||||
а screener получает narrow access contract через `useExisting`
|
||||
(`backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:50-62`;
|
||||
`backend/src/modules/investments/instruments/instruments.module.ts:24`).
|
||||
Это не второй client instance и не доказательство ненужности интерфейса.
|
||||
- **OCP/LSP:** not applicable для исследованных бизнес-границ: сменяемых
|
||||
production implementations одного domain contract не установлено.
|
||||
Stock/bond services — разные capabilities, а не заявленные взаимозаменяемые
|
||||
subclasses. Нарушение LSP по различию их ответов не придумано.
|
||||
|
||||
### 3.6. Границы покрытия
|
||||
|
||||
Выборка risk-/cost-based: пять central capabilities, все Nest registrations,
|
||||
frontend import graph, три подробных page/query consumers (Account, Bonds,
|
||||
AccountEvents), auth/connection boundaries и banking write workflow.
|
||||
Основные гипотезы проверены по caller, source mechanism и контрпримеру.
|
||||
Не подсчитывались LOC savings, complexity scores или количество API calls.
|
||||
Не исследовались подробно UI state/rendering, duplicate fragments,
|
||||
test effectiveness, scheduler error correctness, API latency, dependency
|
||||
security, migrations и эксплуатационные данные. Эти области не закрываются
|
||||
данным audit 01.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### ARCH-002 — Ограничить read orchestration операции запрошенным missing interval
|
||||
|
||||
- **Decision:** FIX.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/income/income-analytics.controller.ts:24-79`;
|
||||
`backend/src/modules/investments/income/income-analytics.service.ts:45-93`;
|
||||
`backend/src/modules/investments/operations/application/brokerage-operations.service.ts:120-150,391-435`;
|
||||
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46-124`;
|
||||
`specs/openspec/specs/tbank-data-sync/spec.md:9-22`;
|
||||
`specs/openspec/specs/brokerage-account-ledger/spec.md:131-144`.
|
||||
- **Runtime consumer:** frontend income queries не передают `sync=observe`
|
||||
(`frontend/src/entities/income/api/useIncome.ts:14-44`). IncomeController
|
||||
передаёт `observe=false`, затем summary/analytics вызывают `collectRanges`.
|
||||
Второй caller `readPlan` — production Ledger read через
|
||||
`backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22-38`.
|
||||
- **Claim:** разрешение читать personal result сейчас включает право
|
||||
инициировать более широкую личную синхронизацию. Граница first-load не
|
||||
удерживается общим application owner.
|
||||
- **Current mechanism:** при обычном Income GET `collectRanges` запрашивает
|
||||
membership, а после завершения запускает readPlan с `history: true`.
|
||||
Ledger тоже передаёт `history: true`. Общий readPlan сначала загружает
|
||||
missing target, затем вычисляет `accountHistory` и ставит в работу
|
||||
`remaining` вне targets. Это отдельная ветвь execution, не только metadata.
|
||||
Все production callers `readPlan` найдены: Ledger и collectRanges.
|
||||
- **Impact / actual cost:** экран аналитики может инициировать membership
|
||||
refresh; узкий Ledger interval может инициировать постороннюю историю
|
||||
счёта. Ответ продолжает возвращаться локально, но ownership работы и объём
|
||||
разрешённого side effect отличаются от narrow exception. **Impact
|
||||
unmeasured:** число фактических RPC, latency и частота этого состояния
|
||||
не измерялись.
|
||||
- **Why now:** source owners уже вызваны hourly coordinator
|
||||
(`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:113-175`).
|
||||
Предыдущая broad-history policy пережила переход и создаёт вторую причину
|
||||
выполнения work.
|
||||
- **Minimum change:** убрать membership/scheduling из обычного income read;
|
||||
оставить ему local operations projection. В общем readPlan убрать branch
|
||||
расширения на `accountHistory`, сохранив coverage, first missing target,
|
||||
revision/access checks и source request. Повторный refresh mutable tail
|
||||
остаётся у hourly. Cold historical income должен честно возвращать
|
||||
incomplete/unavailable; если нужна самостоятельная first-load возможность
|
||||
для Income, принять это явно как изменение существующего исключения,
|
||||
не спрятать в generic read helper.
|
||||
- **Guarantees at risk:** полнота selected/previous/year comparisons, account
|
||||
access, cursor binding, atomic publication, невыдача частичных totals за
|
||||
полный результат. Удалять весь BrokerageOperationsSyncService, staging или
|
||||
first-load Ledger exception нельзя.
|
||||
- **Counterarguments / trade-offs:** history prefetch делает Income cold start
|
||||
удобнее; это реальная продуктовая польза, а не бессмысленная indirection.
|
||||
Полный сохранённый прошлый target сам по себе не повторно загружается:
|
||||
`planOperationRead` добавляет только missing fragments
|
||||
(`backend/src/modules/investments/operations/domain/operations-sync-plan.ts:174-195`).
|
||||
Поэтому finding не утверждает безусловный refresh всех интервалов.
|
||||
Старое ledger requirement всё ещё описывает upstream TTL
|
||||
(`specs/openspec/specs/brokerage-account-ledger/spec.md:97-110`), а новое
|
||||
`:131-140` задаёт first-load/local reads: конфликт нужно сделать явным.
|
||||
- **Verification or rollback:** controlled integration case с membership старше
|
||||
TTL и narrow missing historical interval: Income GET не создаёт membership
|
||||
work; Ledger work ограничен этим interval; complete previous interval
|
||||
остаётся local; incomplete income не превращается в ноль. Проверить оба
|
||||
callers общего helper, не только IncomeController. Откат source policy
|
||||
совместим со schema; product clarification — в существующем ADR/spec.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** ARCH-006.
|
||||
|
||||
### ARCH-003 — Вернуть периодическое чтение владельцу query
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:7-35,37-61`;
|
||||
`frontend/src/features/tbank-connection/index.ts:1-2`;
|
||||
`frontend/src/entities/bond/api/queries.ts:97-104`;
|
||||
`frontend/src/entities/portfolio/api/usePortfolio.ts:19-25`;
|
||||
`frontend/src/app/ui/App.tsx:13-21`.
|
||||
- **Runtime consumer:** Account VM `:78-84`, BondsPage VM `:9-13` и
|
||||
AccountEvents VM `:62-75` по путям из раздела 3. Остальные найденные
|
||||
consumers — Accounts, AccountIncome, AccountOperations, Stock и Bond page
|
||||
VMs. Это прямые вызовы feature public API, а не timer в тестовом коде.
|
||||
- **Claim:** connection feature владеет cadence всех переданных resource GET,
|
||||
хотя их inputs/eligibility/cache policy принадлежат queries. Это competing
|
||||
lifecycle owner, который можно убрать возможностями уже установленного
|
||||
TanStack Query.
|
||||
- **Current mechanism:** hook игнорирует `_scope`, сохраняет resources в ref
|
||||
и каждые 30 минут вызывает manual refetch всех ресурсов. В resource contract
|
||||
нет `isEnabled`; при этом useResourceQuery это поле предоставляет
|
||||
(`frontend/src/shared/api/resource-query.ts:132-146`). Query owners задают
|
||||
enabled/staleTime, App включает focus/reconnect refetch. Timer и эти policy
|
||||
owners действуют независимо.
|
||||
- **Impact / actual cost:** review inputs/query eligibility требует перехода
|
||||
из entity в стороннюю connection feature; Bonds query, отключённый для
|
||||
короткого поиска, всё равно передан timer. Manual refetch не следует
|
||||
automatic enabled policy. **Impact unmeasured:** число нежелательных
|
||||
requests, влияние на backend и время сопровождения не измерены.
|
||||
- **Why now:** screen descriptor больше не выбирает server work; сохранять
|
||||
screen-sync boundary для обычного чтения нет основания. Depcruise это
|
||||
пропускает, потому что `pages → features` разрешён.
|
||||
- **Minimum change:** перенести polling в options существующих query owners
|
||||
через `refetchInterval` и убрать page timer calls/неиспользуемые descriptors.
|
||||
Connection feature оставить владельцем только PUT/DELETE и успешного
|
||||
connection cache reset. Retry отдельного query остаётся в page VM. Если
|
||||
остаётся реальный общий retry use case, сохранить только его cohesion,
|
||||
не переносить timer целиком в новый общий hook.
|
||||
- **Guarantees at risk:** no polling для disabled/unauthenticated queries,
|
||||
отсутствие old-session/old-connection данных, сохранение cached successful
|
||||
result, background-tab semantics и ограничение частоты. Нельзя выдавать
|
||||
механическую замену таймера за полностью эквивалентный scheduling contract.
|
||||
- **Counterarguments / trade-offs:** общий screen timer сейчас синхронно
|
||||
предлагает чтение нескольких resources, что удобно для page composition.
|
||||
Query-native polling по observers может иначе вести себя в background tab
|
||||
и не делает multi-resource read атомарным. Source cadence и согласованность
|
||||
resource results принадлежат backend; общий frontend timer их всё равно
|
||||
не гарантирует. Product правило «не чаще 30 минут» нужно согласовать с
|
||||
mount/focus/reconnect, а не просто добавить второй interval.
|
||||
- **Verification or rollback:** fake-time/MSW через существующую boundary:
|
||||
короткий поиск и отсутствующий account/uid не вызываются, active query
|
||||
повторяется в принятом режиме, session/connection switch отменяет старый
|
||||
результат, повтор не инициирует backend screen command. Проверить focus и
|
||||
hidden-tab behavior. Откат query options/page imports, schema не меняется.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** ARCH-002, ARCH-005, ARCH-006.
|
||||
|
||||
### ARCH-005 — Сохранить разделение session owner и authenticated transport
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/features/auth/model/auth.vm.ts:29-115,121-151`;
|
||||
`frontend/src/shared/api/client.ts:28-35,53-119,122-125`;
|
||||
`frontend/src/shared/api/authenticated-query.ts:34-50`;
|
||||
`backend/src/modules/auth/application/auth.service.ts:166-201`;
|
||||
`backend/src/modules/auth/presentation/auth.controller.ts:81-118`.
|
||||
- **Runtime consumer:** App монтирует AuthProvider
|
||||
(`frontend/src/app/ui/App.tsx:31-37`); все generated authenticated requests
|
||||
проходят shared fetch, session-aware queries используют его session
|
||||
snapshot. Backend refresh endpoint вызывает CAS rotation и после успеха
|
||||
пишет cookie.
|
||||
- **Claim:** queue/single-flight/callback boundary здесь защищает реальные
|
||||
session transitions; это не пустой wrapper над fetch.
|
||||
- **Current mechanism:** auth VM владеет in-memory access token, cookie-write
|
||||
queue, refresh promise и generation; transport добавляет Bearer и делает
|
||||
ограниченный auth retry через callback. Backend conditional update сравнивает
|
||||
прежний refresh hash и требует одного победителя.
|
||||
- **Impact / actual cost:** frontend feature и shared transport имеют разные
|
||||
причины изменения без source import shared → feature. Сохранена граница
|
||||
session policy/HTTP mechanics. **Impact unmeasured:** runtime savings или
|
||||
полная race coverage здесь не измерялись.
|
||||
- **Why now:** при ARCH-003 нельзя одновременно упростить native polling и
|
||||
удалить session-aware eligibility/late-response protection.
|
||||
- **Minimum change:** не менять auth owner/queue ради сокращения файлов;
|
||||
не добавлять refresh loops в pages/entities.
|
||||
- **Guarantees at risk:** identity isolation, один refresh winner,
|
||||
недопущение old-session response, cookie ordering и logout cache clearing.
|
||||
- **Counterarguments / trade-offs:** эта модель ограничена текущей вкладкой;
|
||||
сама по себе не решает межвкладочную coordination. Это documented ceiling,
|
||||
а не основание добавлять distributed token store/Web Locks.
|
||||
- **Verification or rollback:** при будущих изменениях — существующие
|
||||
`frontend/src/features/auth/model/auth.test.tsx`,
|
||||
`frontend/src/shared/api/client.test.ts` и configured backend auth/SQLite
|
||||
scenarios. В этом аудите tests не запускались; rollback не требуется.
|
||||
- **Effort:** S (сохранение existing boundary, без текущего patch).
|
||||
- **Related finding IDs:** ARCH-003, ARCH-006.
|
||||
|
||||
### ARCH-006 — Сохранить singleton source owners и fenced publication
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/integrations/tbank/tbank.module.ts:10-25`;
|
||||
`backend/src/modules/investments/instruments/instrument-sources.module.ts:12-21`;
|
||||
`backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116-184`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35-46,80-159`;
|
||||
`backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`.
|
||||
- **Runtime consumer:** hourly вызывает source owners, personal readers
|
||||
используют их snapshots/status, Events/StockIncome/BondIncome используют
|
||||
общий forecast publisher. Connection replacement — отдельный producer
|
||||
новой revision, а не обход publication owner.
|
||||
- **Claim:** DI/store split и conditional publication защищают реальные
|
||||
source lifecycles. Удалять их вместе с legacy dispatcher нельзя.
|
||||
- **Current mechanism:** Tbank client/access/membership зарегистрированы у
|
||||
одного module owner; source work имеет user/revision/attemptId;
|
||||
portfolio publication claim и write находятся в transaction; forecast
|
||||
проверяет membership/portfolio/source versions. Sync repository module
|
||||
отделяет durable store от coordinator, устраняя обратный module edge.
|
||||
- **Impact / actual cost:** сохраняется общий client/limiter и один publication
|
||||
fence вместо переносов cache/private state в consumers. **Impact unmeasured:**
|
||||
производительность и полнота lifecycle correctness не подтверждены этим
|
||||
архитектурным проходом.
|
||||
- **Why now:** ARCH-002 ограничивает read initiators, а не независимые source
|
||||
owners; ARCH-008 не должен размножить publisher по каждому forecast.
|
||||
- **Minimum change:** сохранить concrete owners, module registration и DB
|
||||
fences; исправления scheduler/policy выполнять внутри текущих границ.
|
||||
Не вводить interface/adapter на каждый provider и не сливать user connection
|
||||
lifecycle с low-level RPC client.
|
||||
- **Guarantees at risk:** connection isolation, CAS/conditional publication,
|
||||
complete snapshot preservation, shared retry/limiter и process lifecycle.
|
||||
- **Counterarguments / trade-offs:** global Prisma provider и concrete DI не
|
||||
равны идеальной строгой layer inversion, но соответствуют существующей
|
||||
single-process monolith topology. ProcessRunId не distributed lease;
|
||||
scheduler прямо фиксирует ceiling
|
||||
(`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:28-31`).
|
||||
Multi-process deployment потребовал бы отдельного решения, не текущего cleanup.
|
||||
- **Verification or rollback:** будущий patch обязан доказать late publication
|
||||
после replacement, superseded attempt и rollback на изолированной SQLite;
|
||||
сохранить target source-owner contracts. В аудите — static verification,
|
||||
не proof реальных races; rollback не требуется.
|
||||
- **Effort:** S (без изменения защищённой boundary).
|
||||
- **Related finding IDs:** ARCH-002, ARCH-003, ARCH-005, ARCH-008, ARCH-009.
|
||||
|
||||
### ARCH-007 — Сохранить cohesive banking transaction и точные money rules
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94-240,251-330`;
|
||||
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35-66,96-123`;
|
||||
`backend/src/modules/banking/bank-cards/domain/rub-money.ts:12-38`;
|
||||
`backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.ts:12-35`;
|
||||
`frontend/src/entities/bank-card/api/mutations.ts:29-82`.
|
||||
- **Runtime consumer:** BankCardsController вызывает journal operations и
|
||||
summary use case; frontend mutations передают intent и после server outcome
|
||||
invalidируют bank GET subtree.
|
||||
- **Claim:** DB transaction callback с concrete Prisma tx client и локальные
|
||||
domain helpers — достаточная граница для банковского use case. Декомпозиция
|
||||
каждой validation/query в отдельный service не даст доказанной пользы.
|
||||
- **Current mechanism:** create intent fingerprint сравнивается после owner
|
||||
check, tombstone не позволяет replay recreate; refund validation и version
|
||||
claim выполняются внутри той же write boundary. RUB normalization переводит
|
||||
decimal string в bigint-копейки с проверками currency/scale/range. Summary
|
||||
получает согласованные inputs через repository и вызывает pure calculation.
|
||||
- **Impact / actual cost:** сохраняется локально обозримый атомарный workflow
|
||||
без broker coupling. **Impact unmeasured:** DB latency, maintenance cost и
|
||||
фактические concurrent outcomes не измерялись.
|
||||
- **Why now:** audit complexity не должен объявлять fingerprints/CAS/tx лишней
|
||||
indirection или переносить journal authority во frontend.
|
||||
- **Minimum change:** сохранить transactional owner и money boundary;
|
||||
не вводить generic repository, port на каждый query или module на каждую
|
||||
banking entity. Read-side optimizations — отдельный предмет audit 07.
|
||||
- **Guarantees at risk:** точность денег, refund limit, rollback, user ownership,
|
||||
lost-response replay protection, immutable operation identity и versions.
|
||||
- **Counterarguments / trade-offs:** repository сейчас также возвращает
|
||||
application conflicts (`card(active)`), а CRUD service делает direct queries.
|
||||
Это documented local compromise, не чистая domain-only модель. Перенос
|
||||
ради одинаковой структуры без самостоятельного consumer только создаст
|
||||
passthrough layer и может разорвать transaction.
|
||||
- **Verification or rollback:** при изменении — существующие configured
|
||||
banking SQLite cases для refund, intent replay, versions/rollback и pure
|
||||
money boundary. Отсутствие запуска здесь явно сохраняется как limitation;
|
||||
текущий patch/rollback не нужен.
|
||||
- **Effort:** S (сохранить, без нового кода).
|
||||
- **Related finding IDs:** —.
|
||||
|
||||
### ARCH-008 — Уточнить event-specific boundary общего forecast publisher
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152-173,271-299`;
|
||||
`backend/src/modules/investments/portfolio/application/forecast.service.ts:18-65,98-123`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79,179-186`;
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:85-94`;
|
||||
`backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:116-125`.
|
||||
- **Runtime consumer:** EventController → Events.get и scheduler
|
||||
refreshEvents → Events.get; ещё два независимых consumers generic publisher —
|
||||
StockIncome и BondIncome.
|
||||
- **Claim:** распределение event semantics между Events, generic
|
||||
ForecastService и repository доказано. Выгода отдельной write/read boundary
|
||||
и конкретное правило latest/window selection — **UNVERIFIED**.
|
||||
- **Current mechanism:** Events кодирует account/window в scopeKey; repository
|
||||
раскодирует его для overlapping fallback; ForecastService снова
|
||||
раскодирует для event filtering и пересчитывает currency totals.
|
||||
`result<T>` одновременно resolve/publish и response projection. GET
|
||||
сохраняет candidate, хотя не делает upstream RPC.
|
||||
- **Impact / actual cost:** изменение смысла event window требует учитывать
|
||||
три owners/файла, generic storage знает private wire-like scope protocol.
|
||||
**Impact unmeasured:** стоимость поддержки и runtime failure rate не
|
||||
измерены; это не основание объявлять общий publisher бесполезным.
|
||||
- **Why now:** hourly owner и local GET нуждаются в понятном разграничении
|
||||
«публиковать новый полный forecast» и «отфильтровать сохранённый». Оно
|
||||
важнее механического перемещения файлов portfolio → events.
|
||||
- **Minimum change:** сначала определить latest-complete forecast identity
|
||||
и window semantics. Если подтверждено, перенести только event window/filter
|
||||
policy в существующий Events owner и дать repository явный selection input;
|
||||
общий conditional publisher и stock/bond consumers сохранить. Новый
|
||||
ForecastModule/ports/CQRS не обоснован.
|
||||
- **Guarantees at risk:** partial result fallback, membership/source-version
|
||||
coherence, точные currency totals, account/all scope и late publication.
|
||||
- **Counterarguments / trade-offs:** общий publisher реально обслуживает
|
||||
несколько forecast capabilities и содержит защитные checks. Local
|
||||
calculation/write внутри GET не равнозначны personal sync/RPC. Repository
|
||||
может обоснованно выбирать fallback по persistence identity; сначала нужно
|
||||
доказать, что текущая window policy не сама intended contract.
|
||||
- **Verification or rollback:** самый дешёвый решающий proof — controlled
|
||||
два последовательных complete forecasts с новым днём, затем narrow и
|
||||
non-overlap GET; проверить время, totals, account/all и preservation при
|
||||
partial update. Correctness вопрос selection передан audit 06. После
|
||||
согласования boundary — reversible local source diff, без удаления schema.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** ARCH-006.
|
||||
|
||||
### ARCH-009 — Не удалять persisted observe path без compatibility evidence
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:66-94,143-174,220-267`;
|
||||
`backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:58-82,112-119`;
|
||||
`frontend/src/shared/api/resource-query.ts:69-127,132-149`;
|
||||
`specs/openspec/specs/online-instrument-information/spec.md:8-17`;
|
||||
`docs/adr/019-hourly-reads-and-online-instrument-reference.md:22-26`.
|
||||
- **Runtime consumer:** HTTP `sync=observe` переводит instrument reads в
|
||||
persisted branch; обычный frontend Bonds query не передаёт этот параметр.
|
||||
Backend financial calendars/candles действительно используют source
|
||||
repository независимо от публичного reference search.
|
||||
- **Claim:** dual reference read policy существует, но отсутствие внешних
|
||||
consumers observe/status и безопасный объём удаления — **UNVERIFIED**.
|
||||
Нельзя считать все persisted instrument sources dead из-за online default.
|
||||
- **Current mechanism:** default detail/search/ticker работают online, observe
|
||||
читает catalog discovery/enrichment snapshots. Frontend resource wrapper
|
||||
всё ещё предоставляет специальный observe method с transport-mode
|
||||
substitution.
|
||||
- **Impact / actual cost:** поддерживаются два пути identity/metadata ответа.
|
||||
**Impact unmeasured:** размер реально устранимого графа, LOC, память и
|
||||
dependency savings не измерены.
|
||||
- **Why now:** online default и public observe используют разные read paths.
|
||||
Для выбора boundary нужно отличить reference compatibility от source данных,
|
||||
необходимых текущим calendars/forecasts/candles.
|
||||
- **Minimum change:** собрать compatibility evidence для public reference
|
||||
observe contract; при подтверждении отсутствия необходимости удалить только
|
||||
его старую discovery orchestration/transport mode. Не удалять общую schema,
|
||||
source rows, financial calendar owner или candle FK без отдельного решения.
|
||||
- **Guarantees at risk:** existing public query contract, instrument identity,
|
||||
persisted financial sources и стабильные labels личных snapshots.
|
||||
- **Counterarguments / trade-offs:** tests и HTTP parameter являются реальными
|
||||
consumers/contract surface, даже если текущая SPA использует online default.
|
||||
Accepted ADR прямо требует отдельной совместимой миграции старых reference
|
||||
rows; отсутствие production call site внутри SPA не доказывает отсутствие
|
||||
других клиентов или FK consumers.
|
||||
- **Verification or rollback:** offline inventory public contract и provider
|
||||
call sites, подтверждение владельцем списка внешних consumers; затем
|
||||
targeted reference HTTP cases и source calendar/candle proof. Production
|
||||
telemetry/реальные API в аудите не запрашивались. Пока решение INVESTIGATE,
|
||||
deletion/rollback не выполняются.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** ARCH-006.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | IDs | Краткий итог |
|
||||
| --- | --- | --- |
|
||||
| DELETE | — | Подтверждённых архитектурных deletion findings в собственном scope нет |
|
||||
| SIMPLIFY | ARCH-003 | Native polling у query owner вместо screen timer в connection feature |
|
||||
| FIX | ARCH-002 | Narrow first-load boundary операций |
|
||||
| KEEP | ARCH-005 | Session owner, single-flight/cookie queue, callback authenticated transport, backend rotation CAS |
|
||||
| KEEP | ARCH-006 | Singleton source owners, lower durable store module, connection/attempt fences и общий conditional publisher |
|
||||
| KEEP | ARCH-007 | Banking transaction/replay/version boundary и decimal/bigint money rules |
|
||||
| INVESTIGATE | ARCH-008 | Event-specific window/selection/read-write ownership; generic publisher имеет реальные consumers |
|
||||
| INVESTIGATE | ARCH-009 | Public observe compatibility и persisted reference orchestration; financial sources не объявлены dead |
|
||||
|
||||
### Valid architectural decisions — специальный результат
|
||||
|
||||
1. **Modular monolith + concrete providers.** AppModule явно собирает
|
||||
capabilities (`backend/src/app.module.ts:18-36`). Установка обязательных
|
||||
ports/adapters на каждый класс не решит найденные initiator conflicts.
|
||||
2. **Отдельные connection lifecycle и integration.** PUT валидирует upstream
|
||||
до DB transaction и атомарно публикует новую revision/membership
|
||||
(`backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31-72`).
|
||||
Это не тот же owner, что shared gRPC protocol/client.
|
||||
3. **Sync repository module ниже coordinator.** Его существование обрывает
|
||||
предполагаемую циклическую зависимость resource → coordinator
|
||||
(`backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:1-9`).
|
||||
Наличие одного класса не делает module лишним.
|
||||
4. **Общий fenced publication, а не независимый write каждого HTTP reader.**
|
||||
Сохранить проверки dependency versions и fallback на complete result
|
||||
(`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35-46,80-159`).
|
||||
5. **Banking journal остаётся локальной транзакционной capability.**
|
||||
Fingerprint, tombstone, version и refund checks имеют разные защитные роли;
|
||||
common transaction сохраняет их совместно — ARCH-007.
|
||||
6. **Frontend layer/public API + верхняя page composition.** Entity queries
|
||||
переиспользуются account/details pages; большой связный VM не нужно
|
||||
дробить только по размеру. Green dependency graph не выбирает owner policy.
|
||||
7. **Private UI/tokens foundation для app и catalog.** Два production/tooling
|
||||
consumers используют один theme entry:
|
||||
`frontend/src/app/ui/App.tsx:7,25-28` и
|
||||
`frontend/.storybook/preview.tsx:14,27-35`.
|
||||
Package boundary здесь оправдана; independent registry/runtime service
|
||||
не требуется.
|
||||
8. **Artifact вместо shared source между repo.** Backend владеет исполняемым
|
||||
Nest contract; frontend использует предоставленный JSON и offline types,
|
||||
specs — behavioral requirements, docs — explanations
|
||||
(`backend/scripts/generate-openapi.cjs:19-35`;
|
||||
`frontend/scripts/check-openapi.mjs:10-26`; `specs/AI_INDEX.md:11-13`).
|
||||
Добавление соседнего checkout в CI не требуется.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| ID | Ожидаемый класс экономии / пользы | Фактическое измерение | Обязательная regression boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| ARCH-002 | Maintenance/предсказуемость side effects; потенциально runtime upstream budget | not measured; реальная frequency/RPC/latency неизвестны | Narrow first-load, access, complete coverage, no partial income totals |
|
||||
| ARCH-003 | Cognitive load, lifecycle ownership; потенциально меньше ненужных GET | not measured; нельзя обещать ускорение или уменьшение requests в процентах | Enabled/session eligibility, polling cadence, cached result/late response |
|
||||
| ARCH-005–007 | Сохранение correctness/security/data guarantees без новых layers | not measured; экономия не заявляется | Auth CAS, connection fencing, banking atomicity/replay/money |
|
||||
| ARCH-008–009 | Возможное уменьшение смешанных semantics/compatibility paths | not measured; решение UNVERIFIED | Complete forecast, public contract, financial source/FK consumers |
|
||||
|
||||
CI-minute saving, dependency removal и runtime ускорение не подтверждены.
|
||||
Никаких новых dependencies для предложенных boundary changes не требуется.
|
||||
Все количества findings подсчитаны по decision rows раздела 4; остальные
|
||||
числа — вывод depcruise или constants исходников с указанными путями.
|
||||
|
||||
**Complexity-only проход ponytail-audit в архитектурном scope:**
|
||||
|
||||
- `native:` screen GET timer/descriptor. Замена: `refetchInterval` существующих
|
||||
query owners, с явной cadence policy.
|
||||
[`frontend/src/features/tbank-connection/model/screen-sync.ts:15-35`, ARCH-003]
|
||||
|
||||
`stdlib:`, `yagni:` и `shrink:` не добавлены ради заполнения списка: одна
|
||||
implementation, небольшой public API или concrete repository сами по себе
|
||||
не являются достаточным основанием удалить boundary. Correctness-related
|
||||
readPlan ограничение находится в ARCH-002, не выдано за вывод skill.
|
||||
|
||||
**net:** возможное сокращение строк **не измерено**; кандидаты на удаление
|
||||
dependencies **не установлены**. Числовой `-N lines` без подготовленного diff
|
||||
был бы выдуманной метрикой.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
Подтверждённые действия сгруппированы по value/risk/effort, а не severity
|
||||
cleanup. Новые архитектурные слои для них не нужны.
|
||||
|
||||
### Quick wins
|
||||
|
||||
Отдельных low-risk quick wins в оставшихся архитектурных findings не
|
||||
установлено: оба actionable изменения требуют boundary proof.
|
||||
|
||||
### Решения с test/product confirmation
|
||||
|
||||
1. **ARCH-002 / M:** сузить общий operations readPlan до missing targets,
|
||||
убрать Income membership/scheduling. До acceptance явно решить cold
|
||||
historical Income UX; proof должен охватить оба production callers.
|
||||
2. **ARCH-003 / M:** передать cadence query owners и убрать connection timer.
|
||||
Согласовать focus/reconnect/mount/hidden-tab смысл правила 30 минут и
|
||||
проверить disabled/session cases существующим MSW/fake-time boundary.
|
||||
|
||||
ARCH-008/009 не представлены как подтверждённые deletion actions: сначала
|
||||
дешёвый semantic/compatibility proof из раздела 9.
|
||||
|
||||
### Minimal boundary changes — специальный результат
|
||||
|
||||
| ID | Текущая boundary | Минимальная достаточная альтернатива | Что сохраняется |
|
||||
| --- | --- | --- | --- |
|
||||
| ARCH-002 | Resource read неявно разрешает membership и broad history scheduling | Income — read-only; Ledger — missing requested interval; hourly — repeat refresh | Existing read service, sync worker, coverage, staging/CAS |
|
||||
| ARCH-003 | Connection feature инициирует timer для unrelated query resources | Query options владеют periodic GET; page VM выбирает resources/inputs; connection feature — lifecycle mutation/reset | Generated keys, shared transport, existing session/connection boundaries |
|
||||
| ARCH-008 (условно) | Generic portfolio forecast знает events window/filter semantics | Events задаёт event selection/projection input; existing publisher сохраняет common fences | Несколько forecast consumers; изменение после semantic proof |
|
||||
| ARCH-009 (условно) | Public reference observe смешан с persisted source machinery | Удалить только доказанно ненужную compatibility orchestration | Financial calendars, candles, private snapshot labels и FK |
|
||||
|
||||
Цена предложений — local source diffs и targeted boundary proof.
|
||||
Дополнительных runtime containers, очередей, generic scheduler, CQRS,
|
||||
обязательных interfaces и новых CI analyzers не предлагается.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **Audit 04 (бывший ARCH-001):** dead-code closure manual dispatcher — `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54-311`; production HTTP entry предоставляет только status: `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31-40`.
|
||||
- **Audit 15 (бывший ARCH-004):** stale Container/read-index относительно hourly ownership — `docs/architecture/containers.md:142-148`, `specs/AI_INDEX.md:15-31`; intended policy: `docs/adr/019-hourly-reads-and-online-instrument-reference.md:9-29`.
|
||||
- **Audit 06:** forecast next-day/fallback selection и GET publication semantics —
|
||||
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50-79`;
|
||||
architectural ownership отмечен ARCH-008, correctness здесь не воспроизводился.
|
||||
- **Audit 06:** hourly termination/persistence outcome —
|
||||
`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:93-110,178-218`;
|
||||
отдельный implementation/lifecycle вопрос внутри сохраняемого owner.
|
||||
- **Audit 06/07:** calendar refresh force/TTL policy —
|
||||
`backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182-239`,
|
||||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:164-193`;
|
||||
не основание удалять source boundary.
|
||||
- **Audit 07:** online search fan-out и banking list loader —
|
||||
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:295-317`,
|
||||
`backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:333-365`;
|
||||
performance не измерялся.
|
||||
- **Audit 08:** ordinary Income GET и scheduling proof вместо одного observe path —
|
||||
`backend/test/tbank-income-local.e2e-spec.ts:249-278`;
|
||||
эффективность suites отдельно не оценивалась.
|
||||
- **Audit 05/13:** timer manual-refetch eligibility при коротком поиске и смене
|
||||
session — `frontend/src/features/tbank-connection/model/screen-sync.ts:28-35`,
|
||||
`frontend/src/entities/bond/api/queries.ts:97-104`;
|
||||
архитектурный owner — ARCH-003, UI/security последствия требуют своего proof.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, который меняет решение | Самая дешёвая решающая проверка | Как меняется решение |
|
||||
| --- | --- | --- |
|
||||
| Может ли cold historical Income самостоятельно загружать прошлые периоды? | Product confirmation существующего tbank-data-sync исключения; controlled test ordinary Income GET с cold coverage/stale membership | ARCH-002 остаётся FIX; либо read-only Income, либо явно расширенное узкое first-load requirement, без membership/full-history prefetch |
|
||||
| Что означает 30-minute client cadence для focus/reconnect/mount/background? | Согласовать формулировку ADR 019 `:27-29`; fake-time/MSW active/disabled query и visibility cases | Определяет query options ARCH-003; не оправдывает connection-owned timer |
|
||||
| Как выбрать latest complete event forecast независимо от запрошенного окна? | Два successive-day complete publications, narrow/non-overlap GET и partial update; проверить account/all totals и timestamps | ARCH-008 может стать SIMPLIFY/FIX с переносом только event-specific policy; общий fenced publisher KEEP |
|
||||
| Нужен ли public instrument `sync=observe` внешнему consumer/старому сохранённому work? | Offline inventory OpenAPI/callers и подтверждение владельцем consumer compatibility; targeted reference/status cases | ARCH-009 становится DELETE/SIMPLIFY только для совместимо удаляемого path; source schema не удаляется автоматически |
|
||||
| Свеж ли предоставленный OpenAPI artifact относительно этих HEAD? | В отдельной разрешённой implementation/verification сессии штатная backend generation/parity и frontend offline check | Здесь сравнение только existing files; новые checker/shared-source dependency не предлагаются |
|
||||
|
||||
**Review completeness перед завершением:** подтверждены девять общих
|
||||
разделов, Ownership map, Dependency/Boundary matrix, valid architectural
|
||||
decisions, minimal boundary changes и финальная таблица. По scoped `rg -c`
|
||||
найдено семь finding headers; каждое из четырнадцати обязательных полей
|
||||
присутствует семь раз, распределение по findings сверено вручную.
|
||||
Actionable findings — только ARCH-002 и ARCH-003; KEEP — ARCH-005–007,
|
||||
INVESTIGATE — ARCH-008–009. Внутренние related IDs ссылаются только на эти
|
||||
семь findings; вынесенные вопросы 04/15 не входят в counts и consolidation.
|
||||
Сверка source evidence/consumer/counterarguments проведена статически;
|
||||
неподтверждённые решения отмечены UNVERIFIED. После сохранения повторены
|
||||
HEAD/status и `git diff --check` во всех пяти checkout: source/config
|
||||
не изменились. Новый ignored отчёт отдельно прошёл whitespace-проверку;
|
||||
routing затрагивает только root audit artifact. Boy Scout review текста
|
||||
не выявил новых source changes, новых механизмов или неподтверждённых числовых
|
||||
оценок экономии. Application build/full-suite для этого artifact не запускались.
|
||||
|
||||
## Final machine-readable consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| ARCH-002 | FIX | P2 | High | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/operations/application/brokerage-operations-sync.service.ts; src/modules/investments/income/income-analytics.service.ts | Personal reads могут инициировать membership и историю вне requested interval | M | ARCH-006 |
|
||||
| ARCH-003 | SIMPLIFY | P2 | High | frontend | src/features/tbank-connection/model/screen-sync.ts; src/entities/bond/api/queries.ts; src/entities/portfolio/api/usePortfolio.ts; src/app/ui/App.tsx | Connection timer конкурирует с query lifecycle owner и не учитывает eligibility | M | ARCH-002,ARCH-005,ARCH-006 |
|
||||
| ARCH-005 | KEEP | P3 | High | frontend,backend | frontend/src/features/auth/model/auth.vm.ts; frontend/src/shared/api/client.ts; backend/src/modules/auth/application/auth.service.ts | Session/transport split сохраняет single-flight, identity isolation и rotation CAS | S | ARCH-003,ARCH-006 |
|
||||
| ARCH-006 | KEEP | P3 | High | backend | src/integrations/tbank/tbank.module.ts; src/modules/investments/tbank-sync/tbank-sync-repository.module.ts; src/modules/investments/portfolio/infrastructure/portfolio.repository.ts; src/modules/investments/portfolio/infrastructure/forecast.repository.ts | Singleton owners и fenced publication сохраняют source lifecycle и connection isolation | S | ARCH-002,ARCH-003,ARCH-005,ARCH-008,ARCH-009 |
|
||||
| ARCH-007 | KEEP | P3 | High | frontend,backend | backend/src/modules/banking/bank-cards/application/bank-operations.service.ts; backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts; backend/src/modules/banking/bank-cards/domain/rub-money.ts; frontend/src/entities/bank-card/api/mutations.ts | Cohesive transaction защищает money, refunds, versions и replay | S | - |
|
||||
| ARCH-008 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/upcoming-events/upcoming-events.service.ts; src/modules/investments/portfolio/application/forecast.service.ts; src/modules/investments/portfolio/infrastructure/forecast.repository.ts | UNVERIFIED: event semantics распределена; новая write/read boundary требует semantic proof | M | ARCH-006 |
|
||||
| ARCH-009 | INVESTIGATE | P2 | Medium | backend,frontend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts; frontend/src/shared/api/resource-query.ts | UNVERIFIED: удаление persisted observe path требует compatibility/FK proof | M | ARCH-006 |
|
||||
@@ -0,0 +1,916 @@
|
||||
# Аудит 02 — Complexity & Overengineering
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID/name:** 02 / Complexity & Overengineering.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Исполнитель:** независимый исполнитель направления 02; другие сабагенты
|
||||
не запускались. Выводы других новых отчётов не читались.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
- **Режим:** исследование, read-only для application source, конфигураций,
|
||||
тестов, specs, docs и root source/config. Единственная запись этого аудита —
|
||||
`audit-results/02-complexity-and-overengineering-report.md` через `apply_patch`.
|
||||
|
||||
| Checkout | Абсолютный путь | Проверенный HEAD | Начальный status |
|
||||
| --- | --- | --- | --- |
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean |
|
||||
|
||||
SHA получены самостоятельно командой `git rev-parse HEAD` из каждого checkout;
|
||||
clean означает пустой вывод
|
||||
`git status --porcelain=v1 --untracked-files=all`, а не отсутствие ignored files.
|
||||
Root allowlist исключает audit-results и nested repos:
|
||||
`.gitignore:1–2`. Поэтому новый отчёт проверяется отдельно от tracked diff.
|
||||
|
||||
**Инструкции и skills.** Сначала загружен установленный `ponytail-audit`
|
||||
через Skill tool, прочитан полный протокол
|
||||
`audit-prompts/02-complexity-and-overengineering.md:1–67`.
|
||||
Использованы его ladder/hunt, теги `delete:`, `native:`, `yagni:`, `shrink:`
|
||||
и ранжирование по возможному сокращению сложности. Полный формат отчёта задан
|
||||
пользовательским протоколом; численная оценка net LOC не выдумывается.
|
||||
Прочитаны AGENTS каждого checkout, local architecture/engineering и
|
||||
активированные frontend UI/data/TS/testing, backend HTTP/data/integrations/
|
||||
security/errors/observability/testing conventions. Root conventions применены
|
||||
только к размещению и проверке отчёта. Загружен
|
||||
`verification-before-completion` для завершающей проверки.
|
||||
|
||||
`graphify` загружен, но существующий graph отсутствует: прямое чтение
|
||||
`graphify-out/graph.json` в root и каждом из четырёх checkout вернуло
|
||||
File not found. Graph extraction, query CLI, установка и создание artifacts
|
||||
не выполнялись. Исследование продолжено статическими трассировками.
|
||||
|
||||
**Что выполнялось:** специализированные Read/Grep/Glob, поиск callers из
|
||||
директорий соответствующих repo, read-only Git-команды для SHA/status;
|
||||
при проверке native-замены прочитан уже установленный dependency source.
|
||||
Завершающие проверки перечислены в разделе 9.
|
||||
|
||||
**Что не выполнялось:** install, codegen, migrations, build, full-suite,
|
||||
targeted runtime tests, запуск приложения, browser/network сценарии,
|
||||
обращения к реальным внешним API, commit, PR. Старый
|
||||
`audit-results/technical-audit-2026-10-11.md` прочитан только как список
|
||||
гипотез; его findings, оценки LOC и результаты проверок не перенесены как
|
||||
собственные измерения. Остальные новые отчёты не открывались.
|
||||
|
||||
**Статус доказательств:** High подтверждает механизм и callers по текущему
|
||||
source, а не runtime-воспроизведение. `UNVERIFIED` означает, что конкретная
|
||||
потеря гарантии или безопасный объём удаления ещё не доказаны. У всех оценок
|
||||
выигрыша — `Impact unmeasured`, если отдельно не указано иное.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт:** наибольшая подтверждённая лишняя координация в живом frontend —
|
||||
`useScreenSync`: страницы передают descriptor и массив resources в отдельный
|
||||
timer, хотя query-owner уже управляет lifecycle. Все изученные production
|
||||
callers используют только его побочный эффект. **CPLX-001 / SIMPLIFY / P2.**
|
||||
2. **Факт:** живой Ledger сначала вычисляет интервал, превращает его обратно
|
||||
в строки screen intent, прогоняет через многоцелевой planner и извлекает
|
||||
только operations/screen intervals. Для этого caller достаточно имеющихся
|
||||
`operationIntervals` и `readPlan`. **CPLX-002 / SIMPLIFY / P2.** Удаление
|
||||
всего planner не следует из этого вывода.
|
||||
3. **Факт, небольшая стоимость:** двухcallback presentation VM скринера
|
||||
не владеет state, fetching, lifecycle или отдельным consumer. Локальная
|
||||
замена сохраняет accessibility; это необязательный cleanup.
|
||||
**CPLX-003 / SIMPLIFY / P3.**
|
||||
4. **Риск и недоказанная возможность упрощения:** forecast связывает request
|
||||
window, persisted identity и выбор fallback; online/observe инструментов
|
||||
обслуживают разные модели; income read содержит асинхронное планирование
|
||||
импорта. Смешение моделей доказано, но безопасное удаление всех ветвей —
|
||||
**UNVERIFIED**, поэтому **CPLX-004–006 / INVESTIGATE**, а не массовый delete.
|
||||
5. **Факт:** conditional publication, source-version fences, точная денежная
|
||||
арифметика, durable reservation, банковские fingerprints/CAS/tombstones и
|
||||
стабильные screener snapshots имеют конкретные текущие сценарии.
|
||||
**CPLX-007–010 / KEEP.** Их сокращение до «обычного GET/CRUD/timer» создаст
|
||||
больше проблем, чем снимет сложности.
|
||||
6. **Итог:** подтверждены **3 actionable findings: P0=0, P1=0, P2=2, P3=1**;
|
||||
отдельно **KEEP=4**, **INVESTIGATE=3**. Новые слои, общий workflow engine,
|
||||
generic repository или новая dependency для решения не нужны. Это
|
||||
risk-/cost-based выборка, а не сертификат отсутствия других проблем.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Фактическая карта и метод
|
||||
|
||||
Проверены независимые checkout и composition roots. Backend — NestJS modular
|
||||
monolith, Prisma/SQLite, конкретные application providers; frontend —
|
||||
React/TypeScript/Vite, TanStack Query, Mantine, generated OpenAPI types.
|
||||
Основания: `backend/src/app.module.ts:18–38`,
|
||||
`backend/package.json:40–66`, `frontend/package.json:39–62`,
|
||||
`frontend/src/app/ui/App.tsx:13–44`. Архитектурная convention сама по себе
|
||||
не использована как доказательство целесообразности реализации.
|
||||
|
||||
Отбор выполнялся по распределению transitions, глубине делегирования,
|
||||
вариантам режима чтения и числу преобразований одного input. Затем проверялись
|
||||
entrypoint, runtime consumer, source-owner, persistence и контрпример.
|
||||
Ни LOC, ни наличие отдельного файла не являлись критерием дефекта.
|
||||
|
||||
Выборка репрезентативна для сложной investment coordination и финансовых writes:
|
||||
есть browser query lifecycle, periodic producer, conditional persistence,
|
||||
cursor/history, multi-period analytics, online reference, command snapshot
|
||||
и transactional replay. Auth refresh, все integration parsers, все UI forms,
|
||||
весь банковский read layer и каждый controller exhaustively не трассировались.
|
||||
Мёртвый код, DRY, tooling/test infrastructure и performance исследовались
|
||||
только до передачи signal соответствующему направлению. Generated/vendor
|
||||
деревья не сканировались как собственные production abstractions проекта.
|
||||
|
||||
**Метод чисел в этом отчёте:**
|
||||
|
||||
- 8 flows — ручная нумерация F1–F8 ниже, по различным production entrypoints.
|
||||
Это не автоматически рассчитанный coverage percentage.
|
||||
- 8 production call sites `useScreenSync` — уникальные page VM из поиска
|
||||
`useScreenSync` в `frontend/src`, без import/export и test matches; все
|
||||
восемь VM прочитаны. Это accounts, account, account-operations,
|
||||
account-events, account-income, bonds, bond, stock.
|
||||
- 2 effects и 1 interval в hook — декларации в
|
||||
`frontend/src/features/tbank-connection/model/screen-sync.ts:23–35`.
|
||||
Это локальные конструкции, а не число browser timers во всей системе.
|
||||
- 2 callbacks presentation VM — декларации строк 9 и 13; 3 consumers forecast
|
||||
— UpcomingEventsService, StockIncomeService, BondIncomeService по imports
|
||||
и вызовам `forecasts.result` в production source.
|
||||
- Counts findings/severity получаются из consolidation table. Число ветвей
|
||||
всего repo, cyclomatic complexity, milliseconds, CI minutes, token savings,
|
||||
bundle size и net LOC не измерялись.
|
||||
|
||||
### 3.2. Восемь живых production flows
|
||||
|
||||
| Flow / owner | Реальный entrypoint и цепочка | Поддерживаемые сценарии и состояния | Сопоставление сложности со сценарием |
|
||||
| --- | --- | --- | --- |
|
||||
| **F1. Обзор счёта и перечитывание**; page VM + entity queries | Route `/accounts/:accountId`: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:203–210` → `pages/account/ui/AccountPage/AccountPage.tsx:9–34` → VM `:66–135` → `entities/portfolio/api/usePortfolio.ts:19–25` → `shared/api/resource-query.ts:99–149` → `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:25–31` → `application/portfolio.service.ts:113–231` → `infrastructure/portfolio.repository.ts:25–47` | 5 resources в overview; без content loading/error, успешный empty/loaded, partial metadata, cached content при background read | Query/session/resource projection и локальный server read оправданы. Отдельный screen timer дублирует query scheduling; scope не влияет на него. CPLX-001 |
|
||||
| **F2. Часовой обход**; scheduler и source owners | `backend/src/app.module.ts:35` → `tbank-sync/tbank-sync.module.ts:24–25` → `tbank-hourly.scheduler.ts:48–87` → `TbankSyncRepository.reserveHourly` `tbank-sync.repository.ts:174–220` → membership/portfolio/operations/events `scheduler:113–175` → settle `:178–218` | Первая/последующая попытка, overlap, restart, changed connection, failed/partial/succeeded; проход membership → portfolios → operations → events | Нативный timer только предлагает работу; durable row и process flag имеют разные горизонты жизни. Polling отдельных workers и completion promises различаются по owner. KEEP координации; ошибка lifecycle передана audit 13 |
|
||||
| **F3. Сохранённый прогноз событий**; UpcomingEvents/Forecast owners | `backend/src/modules/investments/upcoming-events/upcoming-events.controller.ts:22–38` → `upcoming-events.service.ts:54–173,200–239` → local portfolio + saved calendars → `portfolio/application/forecast.service.ts:18–95` → `portfolio/infrastructure/forecast.repository.ts:24–175` | One account/all, известные/неизвестные monetary values, complete candidate, прежний full result при failed dependencies, другое UTC-окно | Фильтрация/расчёт и publication fence — разные обязанности. Парсинг окна из строкового ключа и поиск пересекающегося fallback смешивают read selection и publication. CPLX-004; fences CPLX-007 |
|
||||
| **F4. Ledger, cursor и первая загрузка интервала**; Operations owner | `backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–38` → `application/brokerage-operations.service.ts:69–152` → screen planner → `application/brokerage-operations-sync.service.ts:46–126` → domain interval/coverage → repository → saved page и cursor `service:152–388` | One account/all, explicit/default period, instrument/categories, first/cursor page, complete/missing coverage, historical cold read, moving tail | Подпись cursor и interval normalization сохраняются. Date → ISO → screen → source union → intervals является обходным маршрутом для уже выбранного интервала. CPLX-002 |
|
||||
| **F5. Income summary/analytics**; Income + Operations owners | `backend/src/modules/investments/income/income-analytics.controller.ts:24–79` → `income-analytics.service.ts:45–165` → `operations/application/brokerage-operations.service.ts:391–574` → local snapshot, optional membership.done → schedule → readPlan → totals/comparisons/buckets | Summary выбранного периода, analytics selected/previous/year, неизвестная граница истории, missing/partial/complete, observe/default read | Несколько comparison periods и точные суммы нужны. Два read modes с discovery/schedule расширяют lifecycle обычного чтения; удалить без решения cold history нельзя. CPLX-006 |
|
||||
| **F6. Online поиск/detail/ticker инструмента**; Catalog owner | `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–39` → `entities/bond/api/queries.ts:97–104` → `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89` → `application/bond-catalog.service.ts:25–88` → `instruments/application/catalog-discovery.service.ts:84–337` → TbankClient; `sync=observe` идёт через saved discovery/enrichment | Поиск, UID detail, ticker ambiguity/not found, online и saved/observe variants; bond/share mapping | Catalog service не passthrough: переводит provider money/dates/fields. Legacy observe path доступен через публичный query flag, поэтому отсутствие нового frontend caller не доказывает мёртвость backend ветви. CPLX-005 |
|
||||
| **F7. Скринер акций**; Screener owner | Route `/stocks`: `frontend/src/app/routes/AppRoutes/AppRoutes.tsx:176–183` → `pages/stocks/ui/StocksPage/StocksPage.vm.ts:29–115` → POST/GET `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:85–109` → `application/stock-screener.service.ts:65–139` | Fresh search, stable pages, empty success, expiry, foreign user/revision, invalid page/input, поздний frontend ответ | Snapshot identity, bounds и latest-request guard нужны. Отдельная translation-only presentation VM не добавляет поведения. CPLX-003, CPLX-010 |
|
||||
| **F8. Создание/изменение/повтор банковской операции**; BankOperations owner | `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:133–166` → `application/bank-card-input.ts:125–142` → `application/bank-operations.service.ts:94–330` → `infrastructure/bank-cards.repository.ts:35–73` → SQLite transaction/constraints | Expense/topup/transfer/refund, replay same ID, changed intent, stale version, dependent refunds, delete/tombstone, rollback | Проверки денег/input, replay, ownership, CAS исходной покупки и транзакция защищают разные инварианты. Они не лишние fallback/guards. CPLX-009 |
|
||||
|
||||
Пути в цепочках, сокращённые до `application/...`, относятся к module,
|
||||
названному в той же строке; полные evidence основных решений приведены ниже.
|
||||
Эти flows живы по routes/module wiring/callers; выполнение в production
|
||||
или latency не наблюдались.
|
||||
|
||||
### 3.3. Проверенные seams, guards, flags и контраргументы
|
||||
|
||||
| Кандидат / качественный сигнал | Независимая проверка | Итог |
|
||||
| --- | --- | --- |
|
||||
| Framework interfaces/strategy, конкретные repositories | `backend/src/prisma/prisma.service.ts` и scheduler используют lifecycle contracts; архитектура и DI roots не требуют port на каждый service. Поиск `interface`, `implements`, factory/strategy/registry в `backend/src` не выявил подтверждённого generic plugin engine в выбранных flows | Не объявлять framework seam ненужным из-за одной реализации |
|
||||
| `StockScreenerService` / `TbankAccess` / `TBANK_ACCESS` | Узкий access type `stock-screener.service.ts:50–63`; production `useExisting` `instruments.module.ts:24`; test seam `stock-screener.service.spec.ts:25–38` | Один production provider не означает бесполезность seam; не создавать второй adapter и не считать это actionable |
|
||||
| `GetStockCompanyUseCase` похож на wrapper | `get-stock-company.use-case.ts:25–38,41–88` выбирает sector-dependent metrics, currency/finite value rules и wire projection | Meaningful boundary conversion; не объединять с gRPC client |
|
||||
| Generic `ForecastService` | Три production callers: `upcoming-events.service.ts:160`, `stock-income.service.ts:85`, `bond-income.service.ts:116`; общие publication guarantees | Generic здесь обслуживает существующие разные payloads; не переписывать в три независимых persistence flow |
|
||||
| Generic resource/session wrappers | `frontend/src/shared/api/authenticated-query.ts:34–58`, `resource-query.ts:37–67,99–102,129–149` обслуживают несколько endpoint queries и confirmed access loss | Session gate, cache purge и resource envelope сохранять при CPLX-001/005 |
|
||||
| Presentation VM выглядит формальным | Stocks VM — только labels; `CardPagePresentation.vm.ts:18–53,65–73` имеет настоящий scroll/layout lifecycle | CPLX-003 только для Stocks; не обобщать на все presentation hooks |
|
||||
| Flags `ticking`, durable `state`, `processRunId`, `attemptId` | Tick guard `scheduler:56–71`; durable/restart logic `repository:189–201`; publication identities `portfolio.repository.ts:122–147` | Не одинаковое состояние: обход процесса, lifetime попытки, fencing старой работы |
|
||||
| Public metadata `freshness`, `completeness`, `availability`, `lastAttempt` | `investment-resource.mapper.ts:18–68`; spec `tbank-data-sync/spec.md:24–37` | Один общий boolean уничтожит различие старого полного результата и новой неуспешной попытки. Derived completeness не доказывает, что его можно убрать из wire contract |
|
||||
| Internal `meta.sync` и новые публичные states | `investment-resource.mapper.ts:65–68` задаёт non-enumerable compatibility property; её читают `forecast.service.ts:69–75`, enrichment `:323–328` | Migration bridge имеет runtime consumers; нельзя просто удалить property как «не сериализуется» |
|
||||
| Fallback после неуспеха | `forecast.repository.ts:78–97,130–159` и тест `upcoming-events.e2e-spec.ts:294–318,344–363` | Old complete snapshot и rejection устаревшего candidate нужны; подозрителен выбор identity/window, не само сохранение fallback |
|
||||
| Catch → payload fallback | `forecast.service.ts:98–123` | Parsed string key и generic payload скрывают причинность; malformed saved data/ключи не исследованы runtime. Потенциальное упрощение требует CPLX-004, не механического удаления guards |
|
||||
| Calendar `stable/quote` policy | `catalog-enrichment.service.ts:189–193,679–692`, hourly caller `scheduler:166–175` | Корректность refresh policy — audit 13/07, не доказательство ненужности source-owner |
|
||||
| Runtime guards поверх типов | Screener `item` `:112–127`, money `rub-money.ts:12–31`, company metrics `get-stock-company.use-case.ts:71–87` | DTO/TS types не гарантируют shape внешних records и persisted values. KEEP |
|
||||
| Config switches | `backend/src/config/env.validation.ts:9–19,21–114` ограничивает rate limit, endpoint/CA и logging; реальные integration/client inputs | Не найдено подтверждения speculative compatibility registry; эксплуатационные knobs не удаляются по одному отсутствующему override |
|
||||
|
||||
### 3.4. Complexity justification table — специальные результаты
|
||||
|
||||
| Flow | Supported scenarios | Safety invariants | Suspect complexity | Removal candidate | Risk of change | Decision |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| F1 overview/periodic read | Mounted resources, changing IDs, cached success/error | Enabled/session gate, canonical key, AbortSignal/cache reset, read-only retry | Timer + resourcesRef + screen descriptor над query lifecycle | Собственный scheduler hook после переноса cadence к query options; без новой registry | Medium: background/focus/reconnect и reset cadence не полностью эквивалентны | SIMPLIFY / CPLX-001 |
|
||||
| F2 hourly | First/due/restart/overlap, independent sections | Durable single-attempt, connection isolation, safe terminal outcome | In-memory и durable state рядом; разные completion APIs | Только несущественные presentation/helpers после отдельного lifecycle proof; timer/reservation не удалять | High: restart/late result/partial result | KEEP / CPLX-008 |
|
||||
| F3 forecast | Complete candidate, missing calendar, saved fallback, window/all | Exact Money, atomic dependency validation, membership/connection fence | Window спрятано в scopeKey; generic read selection и publication в одной resolve | Локально разделить latest complete selection и request filter после решения window identity | High: stored data compatibility и сохранённый calculatedAt | INVESTIGATE / CPLX-004; KEEP / CPLX-007 |
|
||||
| F4 ledger | Account/all, default/explicit dates, cursor, first missing range | Exact [from,to), normalization/month splitting, authorization, cursor binding | Range → ISO screen → Source union → intervals | Вызов broad planner в этом reader; напрямую использовать существующий normalizer | Medium: end-inclusive/exclusive, movingEnd и unfiltered source coverage | SIMPLIFY / CPLX-002 |
|
||||
| F5 income | Selected/previous/year, cold/incomplete history, default/observe | Exact per-currency sums, known vs unknown, current connection | Snapshot → membership.done → deferred schedule → readPlan → repeat read | Request-driven membership/schedule только после определения cold-history owner | High: исчезновение первого доступного результата | INVESTIGATE / CPLX-006 |
|
||||
| F6 online instruments | Search/detail/ticker, ambiguity, observe cache | Public shape, unknown fields, source-vs-personal isolation | Online и durable/saved reference paths под одним bool | Legacy observe backend branch только после consumer/contract decision; не calendars/candles | High: публичный flag и persisted consumers | INVESTIGATE / CPLX-005 |
|
||||
| F7 screener | New search, stable page, empty/expired/foreign result | Search ownership/revision, bounds, stable order, latest frontend intent | Translation-only VM; кажущаяся лишней input allowlist | Только Stocks presentation hook; snapshot и raw-body rejection оставить | Low для callbacks; High для snapshots/validation | SIMPLIFY / CPLX-003; KEEP / CPLX-010 |
|
||||
| F8 banking write/replay | Create/update/delete/refund, lost response, collision | Kopecks exactness, transaction rollback, CAS, original fingerprint/tombstone | Много guards и replay-before-validation | Подтверждённых removal candidates в write path нет | High: двойное денежное влияние/over-refund | KEEP / CPLX-009 |
|
||||
|
||||
### 3.5. Калибровка
|
||||
|
||||
**«Сложно, но нужно», случай A — forecast publication (CPLX-007).**
|
||||
Реальный scenario: quantity/source revision меняется между расчётом и записью.
|
||||
Owner повторно проверяет membership, portfolios и calendar versions внутри
|
||||
transaction (`forecast.repository.ts:35–49,80–145`). Изученный SQLite test
|
||||
меняет portfolio до настоящего `resolve`
|
||||
(`backend/test/upcoming-events.e2e-spec.ts:344–363`), а другой меняет
|
||||
dataRevision (`:490–513`). Простое `upsert(candidate)` уменьшает код, но
|
||||
публикует смесь наблюдений. Сохранять fence независимо от решения об окне.
|
||||
|
||||
**«Сложно, но нужно», случай B — banking replay и refund (CPLX-009).**
|
||||
Реальный scenario: полный refund сохранён, HTTP-ответ потерян, одновременно
|
||||
идёт второй refund или исправление покупки. Сначала replay идентичного
|
||||
намерения, затем validation нового действия; CAS покупки и refund sum остаются
|
||||
в transaction (`bank-operations.service.ts:152–196,214–240`). Проверки
|
||||
`bank-cards.e2e-spec.ts:462–489,622–653` различают эти исходы. Проверять
|
||||
refund limit до replay или использовать только unique ID проще внешне,
|
||||
но теряются successful retry и collision protection.
|
||||
|
||||
**«Просто, но рискованно» — убрать raw-body allowlist скринера, полагаясь
|
||||
на `forbidNonWhitelisted` (CPLX-010).**
|
||||
`backend/src/main.ts:31–38` сначала применяет global transform/whitelist без
|
||||
forbidNonWhitelisted. Локальный body pipe
|
||||
`stock.controller.ts:91` работает уже после global pipe; explicit
|
||||
`Object.keys(request.body)` `:94–97` смотрит исходное тело.
|
||||
Порядок проверен по установленному Nest source:
|
||||
`backend/node_modules/@nestjs/core/router/router-execution-context.js:161–170`
|
||||
соединяет `pipes.concat(paramPipes)`,
|
||||
`backend/node_modules/@nestjs/common/pipes/validation.pipe.js:68–85,100–101`
|
||||
преобразует body в entity и возвращает entity для argument.
|
||||
Следовательно, декларация local forbid не является достаточным доказательством
|
||||
rejection неизвестного original field. Изученный configured HTTP test
|
||||
`backend/test/stocks.e2e-spec.ts:73–87` требует 400 для unknown field.
|
||||
Этот test не запускался, но существует конкретный механизм, объясняющий
|
||||
guard. Не удалять guard как redundant; глобальное изменение pipe policy
|
||||
ради одного endpoint увеличит scope и сложность проверки.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
Actionable здесь означает только `DELETE`, `SIMPLIFY` или `FIX`.
|
||||
KEEP — явно защищённая текущая сложность, INVESTIGATE — требование evidence
|
||||
перед изменением. Severity у KEEP=P3 обозначает отсутствие установленной
|
||||
проблемы текущего механизма; серьёзность возможного неправильного удаления
|
||||
описана в Guarantees at risk и не подменяет severity finding.
|
||||
|
||||
### CPLX-001 — native: перенести периодическое чтение к query-owner
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:7–61`;
|
||||
`frontend/src/shared/api/authenticated-query.ts:34–58`;
|
||||
`frontend/src/entities/portfolio/api/usePortfolio.ts:19–25`;
|
||||
`frontend/src/entities/bond/api/queries.ts:97–104`;
|
||||
`frontend/src/app/ui/App.tsx:13–19`.
|
||||
- **Runtime consumer:** eight page VM calls: accounts `AccountsPage.vm.ts:23`,
|
||||
account `AccountPage.vm.ts:78–84`, operations `AccountOperationsPage.vm.ts:96–99`,
|
||||
events `AccountEventsPage.vm.ts:67–75`, income `AccountIncomePage.vm.ts:66–69`,
|
||||
bonds `BondsPage.vm.ts:13`, bond `BondPage.vm.ts:45–55`,
|
||||
stock `StockPage.vm.ts:45–55` под `frontend/src/pages/<page>/ui/<Component>/`.
|
||||
Все восемь используют вызов как statement, не читают возвращённый model.
|
||||
- **Claim:** один read lifecycle сейчас разнесён по query options и собственному
|
||||
page-level timer с mutable ref; это подтверждённая лишняя coordination boundary.
|
||||
- **Current mechanism:** каждый mounted caller ставит interval; отдельный effect
|
||||
обновляет resourcesRef; timer вызывает ручной refetch каждого текущего resource.
|
||||
`_scope` никак не используется. В том же hook рассчитываются refresh/retry/
|
||||
source/transport flags, которые эти production callers не используют.
|
||||
- **Impact / actual cost:** для диагностики cadence нужно проследить page VM,
|
||||
hook, entity policy и App defaults. Проверка enabled находится в query-owner,
|
||||
но timer вызывает manual refetch без неё. **Impact unmeasured**: число лишних
|
||||
запросов, runtime latency и трудозатраты не измерены.
|
||||
- **Why now:** текущие страницы уже перешли к периодическому read-only чтению,
|
||||
descriptor больше не обозначает команду синхронизации.
|
||||
- **Minimum change:** использовать существующий `refetchInterval` на нужных
|
||||
entity query observers вместо timer/ref координации; убрать hook/call
|
||||
descriptors, когда все текущие callers переведены. Не включать polling
|
||||
глобально для всех API и не создавать registry. Явно согласовать текущие
|
||||
focus/reconnect/stale policies у тех же владельцев.
|
||||
- **Guarantees at risk:** auth/session enabled, смена connection/key,
|
||||
cached snapshot/retry, HTTP-only read, отсутствие запросов с пустым UID,
|
||||
прежняя политика background-tab и общий cadence.
|
||||
- **Counterarguments / trade-offs:** единый timer даёт общий момент перечитывания
|
||||
нескольких resources; native timers привязаны к observers и могут менять фазу.
|
||||
Однако текущий hook не выполняет barrier или атомарный snapshot для группы.
|
||||
Native option существует в установленной dependency: `frontend/package.json:46`;
|
||||
`node_modules/@tanstack/query-core/src/queryObserver.ts:371–430` проверяет enabled
|
||||
при scheduling и отдельно управляет background/focus. Manual `refetch`
|
||||
`:279–285,340–362` идёт другим путём. Один refetchInterval **сам по себе не
|
||||
ограничивает** focus/mount/reconnect reads до 30 минут. Переход требует решения
|
||||
о политике, не нового scheduler.
|
||||
- **Verification or rollback:** существующий screen-sync test `:21–80` сохраняет
|
||||
HTTP-only semantics, но не доказывает timer boundary. Перед изменением добавить
|
||||
минимальную MSW/fake-time проверку meaningful/disabled input, смены key,
|
||||
unmount и background/refetch policy; сохранить access/cache tests. Rollback —
|
||||
revert локальной migration, без schema/данных.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** CPLX-005.
|
||||
|
||||
### CPLX-002 — shrink: Ledger не нужен round-trip через общий screen planner
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:74–151`;
|
||||
`backend/src/modules/investments/investment-sync-plan.ts:77–105,121–140,271–284,301–327`;
|
||||
`backend/src/modules/investments/operations/domain/operations-sync-plan.ts:18–64`;
|
||||
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–126`.
|
||||
- **Runtime consumer:** оба живых routes через
|
||||
`backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–38`
|
||||
вызывают `BrokerageOperationsService.read`; после planner consumer берёт только
|
||||
operations источники с priority=screen.
|
||||
- **Claim:** source union и screen abstraction здесь не представляют бизнес-
|
||||
возможность caller; уже выбранный interval временно расширяется до screen
|
||||
plan и снова сужается до того же operations interval.
|
||||
- **Current mechanism:** `ledgerRange` вычисляет [from,to); reader превращает
|
||||
from/to в ISO, вычитает 1 ms из exclusive end, вызывает planner с пустыми
|
||||
instruments и known-membership flag. Planner снова вызывает `ledgerRange`,
|
||||
добавляет membership/history sources, нормализует operations и сортирует
|
||||
priorities. Reader отбрасывает всё, кроме operations/screen, и передаёт readPlan.
|
||||
- **Impact / actual cost:** изменение first-load boundary требует понимания
|
||||
обширного screen vocabulary, priority фильтра и обратимого date conversion.
|
||||
Это engineering/cognitive cost, не доказанный performance bottleneck.
|
||||
**Impact unmeasured.**
|
||||
- **Why now:** live caller уже account/range-based, а screen planner обслуживает
|
||||
модель orchestration, которую read endpoint не исполняет целиком.
|
||||
- **Minimum change:** построить intervals из уже выбранных `selected`, `from`,
|
||||
`to` при known membership и `from < to`, передать их в существующий
|
||||
`operationIntervals`, затем существующий `sync.readPlan`. Сохранить текущий
|
||||
unfiltered source import: instrument/categories ограничивают read result,
|
||||
не должны незаметно сужать completeness базового источника.
|
||||
- **Guarantees at risk:** [from,to) и inclusive HTTP end, subsecond cursor end,
|
||||
empty/all scopes, membership unknown, month splitting, movingEnd,
|
||||
authorization/revision, coverage. Политику history/backfill не менять этим cleanup.
|
||||
- **Counterarguments / trade-offs:** screen planner централизует expansion для
|
||||
другого caller `tbank-sync.service.ts:143–180`; его полное удаление не предлагается.
|
||||
Нормализацию нельзя заменить простым единственным диапазоном: она группирует,
|
||||
объединяет и делит по месяцам (`operations-sync-plan.ts:21–61`).
|
||||
Default planner `force=false` уже не передаёт instrumentUid в import
|
||||
(`investment-sync-plan.ts:139`); это поведение следует сохранить явно.
|
||||
- **Verification or rollback:** сравнить observable first-load intervals до/после
|
||||
для known/unknown membership, one/all, explicit/default end и cursor;
|
||||
использовать изученные SQLite scenarios
|
||||
`backend/test/tbank-ledger-local.e2e-spec.ts:113–137,138–228,229–268`
|
||||
для точности/cursor/local repeat. Они не запускались. Rollback — revert
|
||||
только caller simplification; shared normalizer/repository остаются прежними.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** CPLX-006, CPLX-008.
|
||||
|
||||
### CPLX-003 — yagni: translation-only Stocks presentation VM
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts:1–20`;
|
||||
`frontend/src/pages/stocks/ui/StocksPage/StocksPage.view.tsx:205–208,259–268`;
|
||||
`frontend/.ai/conventions/ui.md:58–89`.
|
||||
- **Runtime consumer:** единственный обнаруженный production import/call —
|
||||
`StocksPage.view.tsx:28,208`. Поиск имени по frontend source не обнаружил
|
||||
другого consumer или самостоятельного test/story seam этого hook.
|
||||
- **Claim:** отдельная VM содержит только два translation callbacks для
|
||||
Pagination labels; собственной lifecycle/data/form responsibility нет.
|
||||
- **Current mechanism:** view уже вызывает `useTranslation`, затем ещё один
|
||||
hook вызывает translation и возвращает getControlProps/getItemProps.
|
||||
- **Impact / actual cost:** дополнительный import/file переход для изменения
|
||||
доступного названия; стоимость мала. **Impact unmeasured**. Не заявляется
|
||||
React performance improvement или необходимость менять всю page architecture.
|
||||
- **Why now:** подходящий cleanup при следующем касании pagination, не отдельная
|
||||
срочная задача и не основание дробить большой view.
|
||||
- **Minimum change:** оставить два типизированных callback references рядом
|
||||
с Pagination в существующем presentation owner, используя уже доступный `t`;
|
||||
убрать отдельный hook/file. Не убирать aria-label и не создавать shared helper.
|
||||
- **Guarantees at risk:** translation update, имена previous/next/page,
|
||||
presentation-only boundary, отсутствие inline functions в JSX props.
|
||||
- **Counterarguments / trade-offs:** local convention предпочитает actions в VM
|
||||
и extraction helpers; формальная uniformity может быть важнее маленького
|
||||
выигрыша. Решение допустимо как узкое UI-only упрощение с именованными const
|
||||
callbacks; если правило трактуется как запрет любых таких callbacks во view,
|
||||
сначала согласовать узкую convention exemption, а не переносить этот код в
|
||||
data VM. `CardPagePresentation.vm.ts:18–53,65–73` — контрпример с настоящим
|
||||
layout lifecycle, его не объединять по аналогии.
|
||||
- **Verification or rollback:** semantic review labels + существующая
|
||||
`StocksPage.test.tsx`; простая проверка keyboard/pagination names при будущем
|
||||
изменении. Новый unit suite только для декларации callback не нужен.
|
||||
Rollback — вернуть файл и import без изменения endpoint/state.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** CPLX-010.
|
||||
|
||||
### CPLX-004 — request window, persisted identity и forecast fallback смешаны
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79,179–186`;
|
||||
`backend/src/modules/investments/portfolio/application/forecast.service.ts:77–93,98–123`;
|
||||
`backend/prisma/schema.prisma:133–145`.
|
||||
- **Runtime consumer:** F3 HTTP reads и hourly `events.get`
|
||||
`tbank-hourly.scheduler.ts:166–175`; тот же ForecastService обслуживает
|
||||
`stock-income.service.ts:85–110` и `bond-income.service.ts:116–140`.
|
||||
- **Claim:** вычисленный request scopeKey используется как PK, query filter
|
||||
и протокол разбора window. Это делает simple saved-read policy зависимой
|
||||
от generic persistence parsing и cross-window fallback. Безопасность замены
|
||||
latest-result моделью — **UNVERIFIED**.
|
||||
- **Current mechanism:** exact-key row → parse requested key → scan matching
|
||||
events keys → first overlapping/account match → membership fallback →
|
||||
candidate/no-candidate branches → version validation → dependency equality
|
||||
→ upsert → повторный parse/filter/re-total в service.
|
||||
- **Impact / actual cost:** read/publication semantics труднее объяснить и
|
||||
менять независимо; catch возвращает исходный payload, если parsing/filter
|
||||
падает. **Impact unmeasured**; не заявляется частота wrong-window response.
|
||||
- **Why now:** новые specs требуют saved calculatedAt/original window при
|
||||
другом request window, включая пустое пересечение
|
||||
`specs/openspec/specs/brokerage-account-upcoming-events/spec.md:10–20,63–76`.
|
||||
- **Minimum change:** пока только определить identity последнего полного
|
||||
результата и разделение read filter/publication в существующих owners.
|
||||
После proof возможна локальная замена cross-window parser/selection;
|
||||
schema migration и универсальный forecast engine не назначаются заранее.
|
||||
- **Guarantees at risk:** сохранённый calculatedAt, window/empty success,
|
||||
source/portfolio version fencing, membership isolation, старые rows,
|
||||
stock/bond payloads и per-currency exact totals.
|
||||
- **Counterarguments / trade-offs:** scope keys нужны для независимых payloads
|
||||
и старых windows. ADR 018 `docs/adr/018-minimal-runtime-coordination.md:42–45`
|
||||
объясняет rejection incompatible candidate; SQLite test
|
||||
`backend/test/upcoming-events.e2e-spec.ts:321–339` требует data=null для
|
||||
непересекающегося window, тогда как новая spec требует empty saved result.
|
||||
Это реальный конфликт evidence, а не разрешение удалить все fallback.
|
||||
`:619–640` отдельно защищает unchanged calculatedAt при узком окне.
|
||||
- **Verification or rollback:** cheapest proof — controlled two successful
|
||||
day/window publications, narrower/disjoint GET, changed dependency перед
|
||||
publish и one/all scope в existing SQLite suite после решения семантики.
|
||||
Проверить representative persisted keys/payloads на disposable данных.
|
||||
Runtime proof и data inspection не выполнялись. До этого rollback-план
|
||||
для изменения identity нельзя считать определённым.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** CPLX-007, CPLX-008.
|
||||
|
||||
### CPLX-005 — online/observe compatibility раздваивает reference lifecycle
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84–140,143–217,220–337`;
|
||||
`backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89`;
|
||||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:233–330`;
|
||||
`frontend/src/shared/api/resource-query.ts:69–127,144`.
|
||||
- **Runtime consumer:** `BondController.search/get/getByTicker` и аналогичные
|
||||
`StockController:58–82,112–119` принимают public `sync=observe`. Новые frontend
|
||||
queries обычно вызывают default online mode; frontend `.observe()` найден
|
||||
только в `resource-query.test.tsx:63,167`, не в production callers.
|
||||
- **Claim:** один public reference use case поддерживает online answer и
|
||||
persisted/durable resource model с иной error/availability semantics.
|
||||
Нужность сохранять backend compatibility branch сегодня — **UNVERIFIED**.
|
||||
- **Current mechanism:** false → online token/search/details/prices; true →
|
||||
saved discovery/source reads, dependency composition, sync metadata и alias
|
||||
verification. Frontend observation helper отдельно меняет transport key,
|
||||
отменяет старый query и пишет envelope в canonical cache.
|
||||
- **Impact / actual cost:** два набора branches/metadata/state policies при
|
||||
изменении search/detail/company; труднее определить владельца freshness.
|
||||
**Impact unmeasured**. RPC fan-out/latency не оценивались в этом направлении.
|
||||
- **Why now:** `specs/openspec/specs/online-instrument-information/spec.md:8–17`
|
||||
и ADR 019 `:22–26` описывают online-only reference, но публичный flag и
|
||||
persistence consumers ещё существуют.
|
||||
- **Minimum change:** получить consumer/compatibility decision для конкретных
|
||||
reference endpoints. При доказанном отсутствии обязательства сохранить old
|
||||
behavior — локально оставить online path и удалить лишь его legacy alternative.
|
||||
Frontend unused observation export передан audit 04, не объявлен отдельным
|
||||
delete finding здесь.
|
||||
- **Guarantees at risk:** public HTTP contract, offline clients, сохранённые
|
||||
labels/calendar/candle dependencies, canonical cache identity и confirmed
|
||||
access invalidation. Не удалять CatalogEnrichment/Repository целиком: F3
|
||||
использует saved calendar sources.
|
||||
- **Counterarguments / trade-offs:** backend branch имеет реальный entrypoint;
|
||||
отсутствие вызова в SPA не доказывает отсутствие внешнего consumer. Shared
|
||||
repository также обслуживает личные forecast dependencies, а старые FK
|
||||
защищают свечи. Generated transport/DTO mapping не является лишним wrapper.
|
||||
- **Verification or rollback:** inventory callers/опубликованного query contract,
|
||||
targeted default-vs-observe HTTP cases на controlled upstream; отдельно
|
||||
проверить calendar/candle source readers. Никаких реальных API calls для proof
|
||||
не требуется. Сохранять совместимость до ответа; data deletion требует
|
||||
отдельного disposable migration/rollback proof и в этот audit не входит.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** CPLX-001, CPLX-007.
|
||||
|
||||
### CPLX-006 — income read содержит собственную асинхронную import coordination
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:391–436`;
|
||||
`backend/src/modules/investments/income/income-analytics.service.ts:45–93`;
|
||||
`backend/src/modules/investments/income/income-analytics.controller.ts:24–79`;
|
||||
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–126`.
|
||||
- **Runtime consumer:** оба summary и оба analytics routes передают
|
||||
`query.sync === 'observe'`; обычный GET выбирает branch с schedule.
|
||||
- **Claim:** read result и запуск работы соединены через snapshot reads,
|
||||
membership completion и deferred schedule. Это дополнительный owner
|
||||
lifecycle личных данных; возможность удалить его целиком без ухудшения
|
||||
cold history — **UNVERIFIED**.
|
||||
- **Current mechanism:** local access → readPage → define schedule → default
|
||||
membership.request → discovery.done.then(schedule) либо await schedule →
|
||||
repeat readPage → periods/coverage/meta → exact income aggregation.
|
||||
- **Impact / actual cost:** response может быть вычислен до завершения
|
||||
deferred work; изменение period/source policy нужно согласовывать с Ledger
|
||||
и hourly owner. **Impact unmeasured**, без оценки RPC/latency.
|
||||
- **Why now:** `specs/openspec/specs/tbank-data-sync/spec.md:9–22` оставляет
|
||||
исключение первой загрузки только Ledger/candles; ADR 019 `:9–16` описывает
|
||||
ту же границу. В live read code сохранилась более широкая координация.
|
||||
- **Minimum change:** определить, кто и как делает отсутствующий history
|
||||
доступным income. Если это уже Ledger/source owner, убрать request-driven
|
||||
membership/schedule из этого reader, сохранив periods/coverage/unknown results.
|
||||
Не создавать новый IncomeSyncService до необходимости.
|
||||
- **Guarantees at risk:** cold-start выбранного и comparison periods,
|
||||
неполнота не превращается в ноль, membership/revision isolation,
|
||||
exact totals и passive observe semantics.
|
||||
- **Counterarguments / trade-offs:** обычное income чтение сейчас может быть
|
||||
единственным действием пользователя для получения старых операций;
|
||||
hourly обновляет mutable tail (`scheduler:154–160`), а не всякий history
|
||||
interval. Механическое удаление ветки оставит данные неполными дольше.
|
||||
Сложность агрегирования по валютам/периодам не является этим finding.
|
||||
- **Verification or rollback:** один controlled cold income GET без observe
|
||||
на пустой coverage и один после Ledger load; проверить lastSuccessfulAt,
|
||||
selected/previous/year, отсутствие membership RPC и old complete reads.
|
||||
Нужен product/behavior answer о доступности истории. Режим работы
|
||||
непроверен runtime; откат локального reader возможен, но UX меняется.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** CPLX-002, CPLX-008.
|
||||
|
||||
### CPLX-007 — сохранить точность и conditional forecast publication
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35–49,80–145,154–174`;
|
||||
`backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:119–130,282–299`;
|
||||
`backend/src/common/decimal.ts:19–48,59–83`;
|
||||
`backend/test/upcoming-events.e2e-spec.ts:294–318,344–363,490–513`.
|
||||
- **Runtime consumer:** F3 плюс stock dividends/bond coupons forecast callers;
|
||||
pure decimal helpers используются для candidate и output totals.
|
||||
- **Claim:** guards отвергают stale/несогласованный candidate; точный Money
|
||||
сохраняет currency и неизвестное значение. Это подтверждённая необходимая
|
||||
сложность, независимо от спорного scope/window selection.
|
||||
- **Current mechanism:** transaction fence connection/membership, portfolio
|
||||
version set, source revision+fetchedAt, прежний result при mismatch,
|
||||
exact bigint+scale calculations, publication только согласованного candidate.
|
||||
- **Impact / actual cost:** сопровождение dependency lists и decimal types
|
||||
стоит внимания; **Impact unmeasured**. Потеря fence создаст неправильную
|
||||
смесь количества и календаря; Number-замена теряет exact decimal semantics.
|
||||
- **Why now:** эти guards находятся рядом с CPLX-004 и особенно уязвимы для
|
||||
ошибочного удаления вместе с parser/fallback logic.
|
||||
- **Minimum change:** оставить эти механизмы; при будущем window cleanup
|
||||
сохранять существующую transaction/version validation boundary.
|
||||
- **Guarantees at risk:** financial accuracy, cross-currency separation,
|
||||
unknown≠zero, immutable successful calculatedAt, connection isolation.
|
||||
- **Counterarguments / trade-offs:** «проверили access до расчёта» не защищает
|
||||
от изменения до записи; test seams здесь доходят до real repository.
|
||||
Batch reads source versions также учитывают SQLite parameter limit
|
||||
(`forecast.repository.ts:99–128`), не заменять без proof одним giant query.
|
||||
- **Verification or rollback:** существующие cited controlled publication
|
||||
scenarios и decimal cases при любом изменении. Они изучены, но не запущены.
|
||||
Удаление protections не предлагается; rollback не требуется этому audit.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** CPLX-004, CPLX-005, CPLX-009.
|
||||
|
||||
### CPLX-008 — сохранить durable reservation и source-owner boundaries
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:28–31,48–111,113–175`;
|
||||
`backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:174–242`;
|
||||
`backend/src/modules/investments/portfolio/application/portfolio.service.ts:244–306`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116–147`;
|
||||
`backend/prisma/schema.prisma:232–262`.
|
||||
- **Runtime consumer:** registered scheduler F2 и portfolio/operations/events
|
||||
source owners; timer не является источником личного HTTP-read lifecycle.
|
||||
- **Claim:** process-level flags/pending maps и durable attempt identity
|
||||
защищают разные случаи. Замена всего на один interval и Promise.all не
|
||||
эквивалентна существующему безопасному publication contract.
|
||||
- **Current mechanism:** tick guard → transaction reservation по current
|
||||
connection/hourly scope → process restart distinction → sequence sources
|
||||
→ own worker completion → conditional publication → durable settlement.
|
||||
- **Impact / actual cost:** диагностика требует нескольких owners;
|
||||
**Impact unmeasured**. Удаление лишит restart/late response fencing и
|
||||
независимого сохранения успеха одного ресурса при сбое другого.
|
||||
- **Why now:** это самая видимая orchestration chain, но она обслуживает
|
||||
текущий hourly requirement и не должна становиться жертвой LOC cleanup.
|
||||
- **Minimum change:** оставить source owners и короткие DB fences; lifecycle
|
||||
ошибки исправлять в них по audit 13, не введением общей distributed workflow
|
||||
платформы. Single-process предел уже отмечен `ponytail:` в source.
|
||||
- **Guarantees at risk:** no overlapping attempts, cancelled old work,
|
||||
current revision ownership, transactions вне network calls, error isolation.
|
||||
- **Counterarguments / trade-offs:** текущий deployment single-process,
|
||||
поэтому новый distributed lease заранее не оправдан. KEEP не означает,
|
||||
что все catch/poll/settle branches корректны: `scheduler:108–110,187–218`
|
||||
дают отдельный reliability signal, который не лечится удалением reservation.
|
||||
- **Verification or rollback:** будущие controlled clock/restart/supersession
|
||||
и persistence-failure checks на durable boundary. Full suites и scheduler
|
||||
runtime не запускались; отсутствие ошибки не утверждается.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** CPLX-002, CPLX-006, CPLX-007.
|
||||
|
||||
### CPLX-009 — сохранить banking intent/CAS/tombstone/rollback
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:152–196,214–240,251–330`;
|
||||
`backend/src/modules/banking/bank-cards/application/bank-card-input.ts:74–81,125–142`;
|
||||
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–73`;
|
||||
`backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`;
|
||||
`backend/prisma/schema.prisma:310–333`.
|
||||
- **Runtime consumer:** F8 create/update/delete HTTP routes
|
||||
`bank-cards.controller.ts:133–166`.
|
||||
- **Claim:** fingerprint, mutable version, original-purchase CAS и deletedAt
|
||||
не являются несколькими способами обозначить одну state; они защищают
|
||||
collision, concurrent edits, refund serialization и resurrection отдельно.
|
||||
- **Current mechanism:** normalize exact input → original fingerprint →
|
||||
transaction → replay existing result before new-operation validation →
|
||||
ownership/links/date checks → purchase CAS/refund sum → write/version/tombstone.
|
||||
- **Impact / actual cost:** guards делают write path длиннее;
|
||||
**Impact unmeasured**. Их удаление допускает второе денежное влияние,
|
||||
over-refund или восстановление deleted record после lost response.
|
||||
- **Why now:** эти механизмы легко ошибочно принять за слишком defensive
|
||||
CRUD, но они требуются `manual-bank-cards/spec.md:527–589` и различаются
|
||||
в studied test outcomes.
|
||||
- **Minimum change:** оставить защищающую transaction и exact arithmetic;
|
||||
не вводить generic repository/IdempotencyService вокруг этой локальной модели.
|
||||
- **Guarantees at risk:** exact RUB kopecks, replay/collision protection,
|
||||
lost update, refund ceiling, ownership, atomic rollback и delete persistence.
|
||||
- **Counterarguments / trade-offs:** unique ID отсекает duplicate row, но
|
||||
сам по себе не различает изменённый intent и replay после edit/delete.
|
||||
Mutex только в приложении не заменит durable version/restart semantics.
|
||||
Retry transaction может повторять callback; держать внешний side effect
|
||||
вне callback — обязательный предел модели.
|
||||
- **Verification or rollback:** `backend/test/bank-cards.e2e-spec.ts:391–422`
|
||||
replay/collision/delete, `:462–489` refund/replay,
|
||||
`:622–653` race и rollback rejected source lock. Тесты прочитаны, не запущены.
|
||||
При будущем изменении нужен real isolated SQLite proof, не mock call count.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** CPLX-007.
|
||||
|
||||
### CPLX-010 — сохранить identity и runtime границы screener результата
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:49–63,65–139`;
|
||||
`backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:85–109`;
|
||||
`backend/src/main.ts:31–38`;
|
||||
`frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts:58–101`.
|
||||
- **Runtime consumer:** F7 POST creates searchId; GET page reads именно этот
|
||||
bounded snapshot, а frontend выбирает результат последнего user intent.
|
||||
- **Claim:** snapshot/ownership/revision/page validation и local late-result
|
||||
guard представляют текущие сценарии; они не оправдывают лишнюю presentation
|
||||
VM, но сами не являются overengineering.
|
||||
- **Current mechanism:** full selected result один раз → bounded Map/TTL →
|
||||
stable paging по searchId → user/current-revision/bounds checks; raw body
|
||||
allowlist поверх transformed DTO; requestId guard на поздние frontend results.
|
||||
- **Impact / actual cost:** bounded memory и дополнительный search identity;
|
||||
**Impact unmeasured**. «Проще запрашивать каталог каждой страницей» теряет
|
||||
стабильность, а удаление guards теряет observable input/owner contract.
|
||||
- **Why now:** этот flow содержит и actionable CPLX-003, и необходимые seams;
|
||||
требуется не смешивать простую presentation simplification с state deletion.
|
||||
- **Minimum change:** сохранить snapshot/guards; не добавлять Redis, cursor
|
||||
service или adapter с единственной реализацией. Узкий `TBANK_ACCESS`
|
||||
`instruments.module.ts:24` имеет test seam и не требует cleanup отдельно.
|
||||
- **Guarantees at risk:** непересекающиеся стабильные страницы, successful
|
||||
empty result, чужой user/revision, expiry, unknown-input rejection,
|
||||
latest frontend search after out-of-order responses.
|
||||
- **Counterarguments / trade-offs:** память process-local, restart теряет
|
||||
search; TTL/limit делают этот предел явным. Default query library нельзя
|
||||
механически поставить вместо command search model. Raw body guard не
|
||||
избыточен при порядке global/local pipes — калибровка 3.5.
|
||||
- **Verification or rollback:** existing
|
||||
`stock-screener.service.spec.ts:40–96` (stable/foreign/revision/invalid cases),
|
||||
configured HTTP `backend/test/stocks.e2e-spec.ts:65–105`.
|
||||
Frontend latest-result proof проверять с completion barrier, не по уже
|
||||
видимому newer result. Все эти tests в этом аудите не запускались.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** CPLX-003.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | IDs | Что сокращать или сохранять | Граница решения |
|
||||
| --- | --- | --- | --- |
|
||||
| DELETE | — | Самостоятельных подтверждённых DELETE findings в scope 02 нет | Отсутствие consumers у manual sync и frontend observe передано 04, не пересчитано как complexity finding |
|
||||
| SIMPLIFY | CPLX-001 | Screen timer/ref/descriptor → existing query scheduling | Только текущие mounted investment reads; auth/cache guards оставить |
|
||||
| SIMPLIFY | CPLX-002 | Date/screen/source round-trip → existing operation interval owner | Не удалять весь planner или менять coverage/history policy |
|
||||
| SIMPLIFY | CPLX-003 | Translation-only VM → local presentation callbacks | Aria labels, typed references и presentation isolation сохранить |
|
||||
| KEEP | CPLX-007 | Exact arithmetic и conditional forecast publication | Даже при изменении identity/window |
|
||||
| KEEP | CPLX-008 | Durable attempt/source-owner coordination | Reliability fixes не равны удалению fence |
|
||||
| KEEP | CPLX-009 | Banking intent fingerprints/CAS/tombstones/transactions | Replay до validation нового refund обязателен |
|
||||
| KEEP | CPLX-010 | Stable bounded screener snapshot, input/owner guards и late-response fence | Не полагаться на local forbid после global whitelist |
|
||||
| INVESTIGATE | CPLX-004 | Forecast window/identity/read selection | UNVERIFIED safe replacement; specs/test expectation conflict |
|
||||
| INVESTIGATE | CPLX-005 | Legacy observe reference lifecycle | UNVERIFIED external consumer/compatibility obligation |
|
||||
| INVESTIGATE | CPLX-006 | Request-driven income schedule | UNVERIFIED cold-history behavior после удаления |
|
||||
|
||||
**Ponytail shortlist, от большего ожидаемого сокращения coordination к меньшему:**
|
||||
|
||||
- `native:` убрать собственный screen read scheduler; использовать установленный
|
||||
Query `refetchInterval` с owner policy. [CPLX-001;
|
||||
`frontend/src/features/tbank-connection/model/screen-sync.ts:22–61`]
|
||||
- `shrink:` убрать Ledger round-trip через screen plan; использовать существующий
|
||||
interval normalizer. [CPLX-002;
|
||||
`backend/src/modules/investments/operations/application/brokerage-operations.service.ts:120–151`]
|
||||
- `yagni:` объединить translation-only presentation hook с его view;
|
||||
сохранить оба accessible callbacks. [CPLX-003;
|
||||
`frontend/src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts:1–20`]
|
||||
|
||||
**net:** сокращение строк не измерено: implementation diff не создавался;
|
||||
подтверждённых dependency-кандидатов на удаление — **0**. Это число означает
|
||||
результат рассматриваемых simplifications, не полный dependency audit.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| ID / группа | Ожидаемый класс экономии | Фактическое измерение | Обязательные регрессии / предел |
|
||||
| --- | --- | --- | --- |
|
||||
| CPLX-001 | Cognitive load, maintenance, меньше собственного scheduling/state code | not measured; runtime/requests/CI minutes/agent tokens не измерены | Enabled/session, changing key, background policy, cached result, retry только GET; нельзя обещать общий 30-minute throttle одним interval |
|
||||
| CPLX-002 | Cognitive load, навигация, maintenance диапазонов | not measured; performance не оценивалась | Date bounds/subseconds/all/unknown membership, normalization, coverage и cursor |
|
||||
| CPLX-003 | Малое сокращение code navigation и maintenance | not measured | Translation/accessibility; отсутствие роста generic UI abstraction |
|
||||
| CPLX-004–006 | Возможное уменьшение mixed lifecycle/compatibility cost | not measured; выгода условна до verification | Public contract, cold history, saved forecast identity и source-owner boundaries |
|
||||
| CPLX-007–010 | Сохранение защищающих гарантий; положительная cleanup экономия не заявляется | not measured; изученный source/test intent не равен runtime proof | Financial accuracy, CAS/transaction/replay/owner isolation, stable paging, lifecycle |
|
||||
|
||||
Не заявлены проценты ускорения, число удаляемых LOC, CI minutes или экономия
|
||||
agent tokens. Снижение навигации — качественное следствие удаления лишнего
|
||||
перехода, а не измерение productivity.
|
||||
|
||||
### 6.1. Before/After logic outline — три подтверждённых случая
|
||||
|
||||
Это варианты изменения, **не внесённые исправления**.
|
||||
|
||||
#### A. CPLX-001 — periodic investment reads
|
||||
|
||||
**Before**
|
||||
|
||||
```text
|
||||
page создаёт entity queries
|
||||
page передаёт scope и resources в useScreenSync
|
||||
effect обновляет mutable resourcesRef
|
||||
другой effect держит timer до unmount
|
||||
tick вручную вызывает refetch каждого resource
|
||||
App/каждый query отдельно задают focus/reconnect/stale policy
|
||||
hook вычисляет ещё refresh/retry/status model, который page не читает
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```text
|
||||
page создаёт те же entity queries
|
||||
owner options задают periodic read interval для этих observers
|
||||
Query enabled/session/key/cleanup владеют запуском и отменой
|
||||
view продолжает читать data/meta/query error
|
||||
retry существующего владельца повторяет только HTTP read
|
||||
focus/reconnect/stale policy согласована у тех же query owners
|
||||
```
|
||||
|
||||
**Потери/добавленная сложность:** у query owners появляются явные interval/
|
||||
background options; observer timers имеют иную фазу, чем один page timer.
|
||||
Если нужно opt-in только для части callers, потребуется минимальный existing
|
||||
query option input. Не создавать новый polling registry или второй scheduler;
|
||||
если обязательна атомарная групповая свежесть, одной native option недостаточно,
|
||||
но текущий hook тоже её не обеспечивает.
|
||||
|
||||
#### B. CPLX-002 — Ledger first-load intervals
|
||||
|
||||
**Before**
|
||||
|
||||
```text
|
||||
access.local → selected accounts
|
||||
ledgerRange(query) → from/to (exclusive end)
|
||||
cursor при наличии задаёт прежний to
|
||||
Date → ISO; to - 1ms → screen intent
|
||||
planInvestmentScreen(operations, empty instruments, membershipKnown)
|
||||
→ membership source + normalized screen operations + history sources
|
||||
→ priority sort
|
||||
filter kind=operations, priority=screen; снова собрать intervals
|
||||
sync.readPlan(intervals, current options)
|
||||
saved page/coverage/cursor
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```text
|
||||
access.local → те же selected accounts
|
||||
тот же ledgerRange и cursor end
|
||||
при membershipKnown и from < to:
|
||||
operationIntervals(selected accounts × already computed [from,to))
|
||||
sync.readPlan(intervals, те же options)
|
||||
тот же saved page/coverage/cursor
|
||||
```
|
||||
|
||||
**Потери/добавленная сложность:** reader должен явно сохранить membership
|
||||
guard и unfiltered source-import semantics. Оставить общую date policy и
|
||||
normalization у прежних helpers; иначе локальное сокращение превратится в
|
||||
новую конкурирующую interval implementation. Изменение history budget не
|
||||
включать в этот before/after.
|
||||
|
||||
#### C. CPLX-003 — Pagination labels
|
||||
|
||||
**Before**
|
||||
|
||||
```text
|
||||
StocksPageView: useTranslation
|
||||
→ useStocksPagePresentationVm: useTranslation
|
||||
→ два typed translation callbacks
|
||||
← callbacks object
|
||||
Pagination получает callback references
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```text
|
||||
StocksPageView: useTranslation
|
||||
два typed callback references рядом с presentation
|
||||
Pagination получает те же aria-label values
|
||||
```
|
||||
|
||||
**Потери/добавленная сложность:** небольшое исключение uniform VM layout и
|
||||
локальные closures в view. Если convention запрещает их абсолютно, выигрыш
|
||||
мал и правило нужно сначала согласовать; не вводить shared utility/data-VM
|
||||
зависимость ради устранения одного файла. Accessibility не упрощается.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
Только подтверждённые actionable направления; investigation не выдаётся за
|
||||
готовое действие удаления. Ранжирование — value/risk/effort, не severity alone.
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **CPLX-002:** локально убрать Ledger → screen-planner round-trip с сохранением
|
||||
`operationIntervals/readPlan` и current options. Самая небольшая содержательная
|
||||
change boundary; обязательна проверка exact interval/cursor semantics.
|
||||
2. **CPLX-003:** при ближайшем изменении Pagination убрать translation-only hook.
|
||||
Небольшая стоимость/выгода; не делать отдельный архитектурный рефакторинг.
|
||||
|
||||
### Решения, требующие policy/test подтверждения
|
||||
|
||||
3. **CPLX-001:** заменить page timer existing Query scheduling и согласовать
|
||||
focus/reconnect/background/key-change policy. Перед implementation нужен
|
||||
маленький controlled boundary proof; ADR нужен только если меняется
|
||||
продуктовый cadence contract, а не ради применения query option.
|
||||
|
||||
CPLX-004–006 требуют ответов раздела 9 до назначения cleanup. KEEP-механизмы
|
||||
не помещены в список «улучшений», чтобы не увеличивать backlog без необходимости.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
Каждый пункт — передача темы владельцу, без второго полноценного аудита.
|
||||
|
||||
| Тема | Evidence | Рекомендуемый audit ID |
|
||||
| --- | --- | --- |
|
||||
| Нет найденного production consumer `TbankSyncService.request`; controller предоставляет только status, shared planner всё ещё нужен Ledger | `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–311`; `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:34–40`; поиск `TbankSyncService` по backend source | 04 — Dead Code & Legacy |
|
||||
| Frontend `.observe()` consumer обнаружен только в tests; backend observe route при этом жив | `frontend/src/shared/api/resource-query.ts:69–127`; `frontend/src/shared/api/resource-query.test.tsx:63,167`; `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89` | 04 — Dead Code & Legacy |
|
||||
| Две publicInstrument projections требуют отдельного DRY ownership review | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–530`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–677` | 03 — Duplication & Reusability |
|
||||
| Общий catch/settle/poll hourly lifecycle и stable calendar refresh policy | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:108–110,187–218`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:189–193,679–692` | 13 — Reliability & Security |
|
||||
| Default income GET/source history ownership и readPlan backfill выходят за complexity cleanup | `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:408–435`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:102–124`; `specs/openspec/specs/tbank-data-sync/spec.md:9–22` | 06 — Backend Architecture; 07 — API/Data Flow |
|
||||
| Overview source metadata не передаётся в loaded view | `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135`; `frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34` | 05 — Frontend Architecture |
|
||||
| Search detail RPC fan-out и banking list operationView повторные reads | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:308–317`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:41–68,361–364` | 07 — API/Data Flow Efficiency |
|
||||
| Forecast old-range expectation конфликтует с новой saved-window spec; документация читает разные поколения owner model | `backend/test/upcoming-events.e2e-spec.ts:321–331`; `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:18–20`; `docs/architecture/containers.md:142–148`; `docs/adr/019-hourly-reads-and-online-instrument-reference.md:9–16` | 08 — Test Quality; 15 — Specs & Documentation |
|
||||
|
||||
Ни один signal не включён в actionable count 02 как отдельное исправление
|
||||
correctness/security/performance/dead code/tests/tooling.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, действительно меняющий решение | Самый дешёвый способ проверки | Влияние на finding |
|
||||
| --- | --- | --- |
|
||||
| Интервал 30 минут относится только к periodic tick или также mount/focus/reconnect; нужна ли background вкладка? | Согласовать смысл `docs/adr/019-hourly-reads-and-online-instrument-reference.md:27–29`, затем controlled Query/MSW fake-time case с существующими policies | CPLX-001: native замена подтверждена, точная cadence policy ещё требует решения |
|
||||
| Ledger нормализация должна быть upstream-unfiltered при UI instrument filter? | Сравнить current planner `force=false` и existing normalizer outputs на one/all, default dates, subsecond cursor и unknown membership | CPLX-002: ограничивает безопасный минимальный diff |
|
||||
| Допустимы ли два trivial presentation callbacks во view при текущей convention? | Узкое semantic решение по `frontend/.ai/conventions/ui.md:58–89`; проверить typed callback refs и aria labels | CPLX-003: может оставить P3 cleanup отложенным без новой abstraction |
|
||||
| Что является identity последнего полного event forecast; какой новый полный результат заменяет старый window? | Согласовать current specs/test conflict; two-window publication + narrow/disjoint read + dependency race в existing SQLite suite | CPLX-004: до ответа safe simplification UNVERIFIED |
|
||||
| Есть ли обязательные внешние consumers saved `sync=observe` reference endpoints? | Проверить предоставленный public contract и сведения владельца о consumers; targeted HTTP default/observe case | CPLX-005: отсутствие SPA consumer не разрешает breaking backend delete |
|
||||
| Кто обеспечивает cold historical income после отказа от request-driven membership/schedule? | Один cold income GET и один после Ledger interval load на isolated fixture; уточнить UX owner истории | CPLX-006: отсутствие полного результата нельзя заменять нулём |
|
||||
|
||||
**Минимальные будущие проверки, не запущенные в этом аудите:** существующие
|
||||
frontend MSW/RTL boundary cases для CPLX-001/003 и isolated SQLite scenarios
|
||||
для CPLX-002/004/006; published contract/consumers для CPLX-005. Не требуется
|
||||
новая audit test infrastructure. Статический audit не доказывает отсутствие
|
||||
временных race или актуальность реальных данных.
|
||||
|
||||
### 9.1. Проверка выполнения самого аудита
|
||||
|
||||
- Полный протокол прочитан; сохранены 9 общих разделов.
|
||||
- В inventory: 8 live flows с entrypoints/owners/callers, complexity justification
|
||||
table, seams/guards/fallback/states/compatibility assessment.
|
||||
- Калибровка: два «сложно, но нужно» и один «просто, но рискованно».
|
||||
- Findings CPLX-001–010 имеют все обязательные поля; actionable/KEEP/INVESTIGATE
|
||||
и unmeasured/UNVERIFIED разделены.
|
||||
- Before/After: три подтверждённых случая и явная added-complexity цена каждой
|
||||
рекомендации. Финальная consolidation table содержит только эти 10 IDs.
|
||||
- Boy Scout semantic review отчёта: проверены scope 02, конкретные владельцы,
|
||||
реальные consumers, контраргументы, preservation точности/transactions/CAS/
|
||||
replay/auth/cache/lifecycle; source cleanup не выполнялся.
|
||||
- После записи выполнены `git rev-parse HEAD`,
|
||||
`git status --porcelain=v1 --untracked-files=all`, `git diff --check` и
|
||||
`git diff --cached --check` из каждого из пяти checkout: HEAD совпадает
|
||||
с таблицей metadata, status/diff checks без вывода. Все source checkout
|
||||
остались clean; staged/unstaged/new source изменений не появилось.
|
||||
- `git check-ignore -v audit-results/02-complexity-and-overengineering-report.md`
|
||||
подтвердил root ignore `.gitignore:2`. Сам новый файл проверен отдельно:
|
||||
`git diff --no-index --check /dev/null audit-results/02-complexity-and-overengineering-report.md`
|
||||
без whitespace errors.
|
||||
- Read/Grep нового отчёта подтвердили разделы 1–9, special results и финальную
|
||||
таблицу. `rg --count` по heading pattern вернул 10 findings; по field-label
|
||||
pattern — 150 полей (14 обязательных + Runtime consumer у каждого из 10).
|
||||
Таблица вручную сверена с полями; aggregation учитывает только 10 строк
|
||||
consolidation, не дополнительные сводные таблицы выше.
|
||||
|
||||
**Ограничения:** static risk-based выборка, без existing graph, без runtime
|
||||
tests/build/full-suite, без реальной сети и persisted production data inspection.
|
||||
Generated schema/lockfiles/tools не менялись; архивы/references/dated audits
|
||||
не изменялись. Package manifests и installed dependency source изучались,
|
||||
но dependency/security/performance audit не выполнялся. Некоторые initial
|
||||
path guesses отсутствовали; последующие evidence взяты только из реально
|
||||
прочитанных paths, не из предположений о layout.
|
||||
|
||||
## Machine-readable consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| CPLX-001 | SIMPLIFY | P2 | High | frontend | src/features/tbank-connection/model/screen-sync.ts; src/entities/portfolio/api/usePortfolio.ts; src/entities/bond/api/queries.ts; src/app/ui/App.tsx | Page timer/ref добавляет второй read lifecycle; эффект не измерен | M | CPLX-005 |
|
||||
| CPLX-002 | SIMPLIFY | P2 | High | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/investment-sync-plan.ts; src/modules/investments/operations/domain/operations-sync-plan.ts | Ledger разворачивает выбранный interval через общий screen plan; эффект не измерен | S | CPLX-006,CPLX-008 |
|
||||
| CPLX-003 | SIMPLIFY | P3 | High | frontend | src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts; src/pages/stocks/ui/StocksPage/StocksPage.view.tsx | Два translation callbacks требуют отдельного файла; эффект не измерен | S | CPLX-010 |
|
||||
| CPLX-004 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/modules/investments/portfolio/application/forecast.service.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Window identity смешана с fallback/publication; безопасная замена UNVERIFIED | M | CPLX-007,CPLX-008 |
|
||||
| CPLX-005 | INVESTIGATE | P2 | Medium | backend,frontend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts; frontend/src/shared/api/resource-query.ts | Online/observe сохраняют два lifecycle; обязательство compatibility UNVERIFIED | M | CPLX-001,CPLX-007 |
|
||||
| CPLX-006 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/income/income-analytics.service.ts; src/modules/investments/income/income-analytics.controller.ts | Income read также планирует импорт; безопасное удаление cold-history пути UNVERIFIED | M | CPLX-002,CPLX-008 |
|
||||
| CPLX-007 | KEEP | P3 | High | backend | src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/common/decimal.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Точная арифметика и conditional publication защищают согласованный monetary snapshot | S | CPLX-004,CPLX-005,CPLX-009 |
|
||||
| CPLX-008 | KEEP | P3 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts; src/modules/investments/portfolio/infrastructure/portfolio.repository.ts | Durable attempts/source fences защищают restart и позднюю публикацию | S | CPLX-002,CPLX-006,CPLX-007 |
|
||||
| CPLX-009 | KEEP | P3 | High | backend | src/modules/banking/bank-cards/application/bank-operations.service.ts; src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts; src/modules/banking/bank-cards/domain/rub-money.ts | Intent/CAS/tombstones/transactions защищают точные replay/refund semantics | S | CPLX-007 |
|
||||
| CPLX-010 | KEEP | P3 | High | backend,frontend | backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts; backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts; frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts | Snapshot и owner/input/late-result guards имеют текущие сценарии и consumers | S | CPLX-003 |
|
||||
@@ -0,0 +1,890 @@
|
||||
# Аудит 03 — Duplication & Reusability
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID/name:** 03 / Duplication & Reusability.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
- **Режим:** независимое read-only исследование; единственный записанный
|
||||
артефакт этого исполнителя — настоящий отчёт вне четырёх checkout.
|
||||
- **Протокол:** `audit-prompts/03-duplication-and-reusability.md`, прочитан
|
||||
целиком. Другие новые отчёты не читались и не перезаписывались.
|
||||
- **Skill:** установленный `ponytail-audit` загружен через Skill tool до
|
||||
исследования. Его complexity-only подход применён к предложениям
|
||||
`SIMPLIFY`; подтверждённое рассогласование validation policy разобрано
|
||||
отдельно по контракту аудита, не приписано correctness-проверке skill.
|
||||
Дополнительно использованы `feature-sliced-design` для ownership,
|
||||
`systematic-debugging` для трассировки причины DRY-001 и
|
||||
`verification-before-completion` для проверки результата исследования.
|
||||
- **Graphify:** skill загружен. По известному абсолютному пути root
|
||||
`graphify-out/graph.json` отсутствует; поиски этого artifact внутри четырёх
|
||||
checkout также не дали результатов. Glob может пропускать ignored paths,
|
||||
поэтому отсутствие любого скрытого графа не доказано. Доступного existing
|
||||
graph для query не обнаружено; анализ продолжен статически. Генерация,
|
||||
установка, индексация и создание graphify artifacts не выполнялись.
|
||||
|
||||
### Репозитории и самостоятельно снятое состояние
|
||||
|
||||
Все пути в таблице абсолютные. В остальных evidence путь начинается с имени
|
||||
checkout и считается относительно workspace.
|
||||
|
||||
| Repo | Path | HEAD SHA | Начальный `git status --short` | После исследования и сохранения отчёта |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | пустой | пустой; отчёт ignored |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | пустой | пустой |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | пустой | пустой |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | пустой | пустой |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | пустой | пустой |
|
||||
|
||||
`AGENTS.md` каждого checkout прочитан до исследования его содержимого.
|
||||
Прочитаны локальные architecture/engineering и активированные темы:
|
||||
frontend data, TypeScript/React, UI, testing, Storybook; backend HTTP API,
|
||||
data, errors, security/auth, integrations, testing. Specs выбирались через
|
||||
`AI_INDEX.md`; docs — через README/CONTRIBUTING и тематические страницы.
|
||||
Root conventions не применялись к соседним checkout. Локальный FSD profile
|
||||
и его реальные import restrictions важнее общего совета о размещении CRUD.
|
||||
|
||||
### Выполненные проверки и границы исполнения
|
||||
|
||||
1. В каждом repo самостоятельно выполнены `git rev-parse HEAD` и
|
||||
`git status --short`: в начале, после исследования и перед завершением.
|
||||
2. Использованы Read/Glob/Grep по конкретным repo paths для поиска символов,
|
||||
callers, требований и существующих тестовых сценариев.
|
||||
3. Выполнен read-only
|
||||
`git diff --no-index --stat backend/openapi.json frontend/src/shared/api/spec/openapi.json`
|
||||
из root: различий нет. Это сравнение existing файлов, а не доказательство
|
||||
свежести генерации относительно runtime.
|
||||
4. Проверены `git diff --check` и отдельно whitespace нового ignored отчёта
|
||||
через `git diff --no-index --check /dev/null audit-results/03-duplication-and-reusability-report.md`.
|
||||
Проверен `git check-ignore -v` для отчёта; root allowlist сохранён.
|
||||
5. Выполнена содержательная сверка отчёта с девятью общими разделами,
|
||||
специальными результатами, всеми finding fields и финальной таблицей.
|
||||
Read-only `rg --count-matches` подтвердил 9 общих заголовков, 10 DRY sections,
|
||||
150 полей (15 на finding, включая Runtime consumer) и 3 Ownership model
|
||||
для 3 SIMPLIFY. Агрегатный подсчёт дополнен чтением каждого блока.
|
||||
|
||||
Тесты не запускались. Node/npm-команды, install, codegen, migrations, build,
|
||||
full-suite/verify, commit/PR и обращения к внешним API не выполнялись.
|
||||
Исходники, конфигурации, snapshots, dated audits, архивы и references не
|
||||
изменялись. Файлы БД, `.env`, секреты и реальные пользовательские данные не
|
||||
читались. Два узких installed-library файла прочитаны только для проверки
|
||||
семантики `MinLength`, не как кандидаты на ручной DRY cleanup.
|
||||
|
||||
`audit-results/technical-audit-2026-10-11.md` использован как набор гипотез:
|
||||
F12 и F15 независимо подтверждены исходниками и callers. Другие прежние
|
||||
рекомендации не принимались автоматически. Числа о LOC, fan-out и времени из
|
||||
него не перенесены как собственные измерения.
|
||||
|
||||
При сводной перепроверке независимо повторён source trace discovery и всех
|
||||
production callers `useScreenSync`. Исправлена прежняя ошибка reachability:
|
||||
catalog HTTP paths не запускают discovery producer, а возвращаемый screen
|
||||
retry не имеет найденных production consumers. DRY-009 сужен до живого
|
||||
enrichment и используемых repository fences. Соседние отчёты не читались;
|
||||
reachability/deletion discovery переданы audit 04, без требования KEEP этого
|
||||
producer. Исходные SHA при перепроверке совпадают с таблицей выше.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт — DRY-001:** одна password policy имеет разные единицы в frontend
|
||||
и backend. UI допускает не-ASCII пароль, который отклоняет API. Это
|
||||
значимое локальное рассогласование, а не основание удалить серверную
|
||||
валидацию или создать межрепозиторную библиотеку validators.
|
||||
2. **Факт — DRY-002:** одинаковая whitelist-проекция instrument поддерживается
|
||||
в двух сервисах одного backend module. У правила один владелец, одинаковые
|
||||
типы, ошибки и причины изменения. Локальный mapper даст самый убедительный
|
||||
reuse при сохранении самого whitelist.
|
||||
3. **Риски сопровождения — DRY-003/004:** frontend повторяет список платёжных
|
||||
систем внутри одной формы и один набор access-loss codes в трёх контекстах.
|
||||
Сегодня копии согласованы; текущих пользовательских дефектов не доказано.
|
||||
Это небольшие P3 cleanup-кандидаты, не обязательная большая миграция.
|
||||
4. **Факт — DRY-005–009:** денежные trust boundaries, stock/bond семантика,
|
||||
session-aware query wrappers, generated API copies и живой enrichment с
|
||||
repository publication fence имеют конкретные гарантии. DRY-009 не требует
|
||||
сохранять discovery `request/refresh/drain`: current HTTP consumers их
|
||||
обходят, найденный caller `refresh` — только недоступный manual dispatch.
|
||||
5. **Непроверенная гипотеза — DRY-010:** часть screen-level periodic-read
|
||||
orchestration может перейти к query owners. Наличие нескольких triggers
|
||||
подтверждено, но равноценность замены и runtime-экономия — `UNVERIFIED`.
|
||||
Все восемь production callers игнорируют hook return; общей кнопки retry
|
||||
из этого hook не найдено. Реальный counterargument — mount-based timer phase
|
||||
с чтением текущего resource array. Разные `staleTime` не доказывают DRY-долг.
|
||||
|
||||
**Итог по реестру:** 4 actionable findings: P0 — 0, P1 — 0, P2 — 1,
|
||||
P3 — 3; отдельно KEEP — 5, INVESTIGATE — 1. Actionable здесь означает
|
||||
`FIX`, `SIMPLIFY` или `DELETE`; DELETE-кандидатов в scope 03 не подтверждено.
|
||||
Подсчёт — по уникальным DRY-ID в разделе 4, без повторного подсчёта строк
|
||||
Knowledge Register и Cross-audit signals. P2 у KEEP означает значимость
|
||||
защищаемого поведения, а не urgency cleanup.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### Фактически прослеженные области
|
||||
|
||||
| Область | Прослеженный flow / объекты | Граница вывода |
|
||||
| --- | --- | --- |
|
||||
| Auth validation | `AuthForm` → Zod schemas → auth provider/client → `CredentialsDto` через configured pipe → `AuthController` → `AuthService`; spec и docs | Установлены разные единицы длины; HTTP-воспроизведение не запускалось |
|
||||
| Catalog projection | Stock/Bond controllers → catalog services → online discovery либо read-only observe; quote/company paths → enrichment section/load | Две одинаковые detail-проекции; catalog HTTP paths не запускают discovery producer |
|
||||
| Banking forms | `form.schema.ts`, fields/form VM, generated bank-card types, backend DTO/input/RUB helpers, operation validation/transaction | Разделены локальная копия словаря и необходимое повторение trust-boundary checks |
|
||||
| Frontend errors | client permanent classification, resource access-group invalidation, Account/Accounts VM и error views | Совпадает один набор codes; recovery actions и Accounts subset различаются |
|
||||
| Stock/Bond UI | Page containers/VM/views, payout components, `instrument-details` model/UI, entity queries | Подтвержден existing reuse и реальная разница dividend/coupon и денежных единиц |
|
||||
| Query identity/lifecycle | `apiQueryClient`, authenticated/resource queries, account/portfolio/stock/bond/bank hooks, connection boundary, screen timer | Не обнаружен competing handwritten key для изученных investment queries; банковский `all` — отдельный агрегат |
|
||||
| Backend state transitions | Discovery entry-point trace отдельно; живой enrichment reservation/drain → repository publishSource/finish | KEEP касается enrichment/shared fence, не discovery producer; reachability/deletion — audit 04 |
|
||||
| Contracts/config | manifests, `main.ts`, `openapi.ts`, frontend dependency-cruiser, API snapshot и точечные generated declarations | Generated files не исследовались как ручные дубли; freshness генерации не доказана |
|
||||
| Calendar/money owners | existing investment calendar, instrument-market, shared display, common decimal, T-Bank protobuf decimal | Не предложена общая библиотека на основании похожего имени или арифметики |
|
||||
|
||||
Выборка risk-/cost-based: приоритет получили публичные проекции, валидируемые
|
||||
политики, money/null semantics, recovery и повторяющиеся transitions.
|
||||
Тесты читались как свидетельства задуманного поведения и кандидаты для
|
||||
будущей проверки, не проводился отдельный аудит их качества или factories.
|
||||
|
||||
**Метод чисел:** количество копий получено symbol/content search с ручным
|
||||
сопоставлением конкретных реализаций и consumers. Для `publicInstrument`
|
||||
найдены две definitions и четыре call sites: discovery `:157,323`, enrichment
|
||||
`:263,349`. Для access-loss набора — три полных места; transport содержит
|
||||
ещё `UNAUTHORIZED`, а Accounts VM использует только два connection codes.
|
||||
Для payment systems — две независимо редактируемые frontend runtime lists;
|
||||
backend tuple и производные artifacts перечислены отдельно. Глобальные
|
||||
проценты дублей, прочитанных файлов, LOC и coverage не вычислялись.
|
||||
|
||||
Восемь production call sites `useScreenSync` получены поиском символа в
|
||||
`frontend/src` с исключением declarations/re-exports/tests и чтением каждого
|
||||
полного VM. Во всех случаях вызов — standalone expression, без присваивания,
|
||||
destructuring или передачи return дальше; таблица ниже фиксирует полный набор.
|
||||
|
||||
### Уточнённый consumer trace: discovery и живой enrichment
|
||||
|
||||
| Вход | Подтверждённая цепочка / evidence | Достигает discovery `request/drain`? |
|
||||
| --- | --- | --- |
|
||||
| Catalog HTTP endpoints | `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:58–82,112–119`; `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:55–89` передают `query.sync === 'observe'`; `backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts:22–89` и `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25–88` вызывают только `search/detail/byTicker` | Нет при обоих значениях observe |
|
||||
| Default `search` | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84–86,295–337`: ранний return `onlineSearch`, RPC напрямую через client | Нет |
|
||||
| Default `detail` / `byTicker` | Тот же файл `:149–166,227–249`: online branches; `byTicker(false)` вызывает `detail` с default false | Нет |
|
||||
| Observe branches | Тот же файл `:86–93,168–190,251–267`: `read(..., true)` и `enrichment.readMany(..., true)`; `read` запускает request только при `!observe` (`:66–70`) | Нет |
|
||||
| Discovery producer entry | Тот же файл `:340–386,395–396`: `request` ставит drain, `refresh` вызывает request. Единственный найденный внешний production-text caller refresh — `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:192–201` внутри `dispatch` | Только dead-parent-reachable по найденному src trace, не текущий HTTP flow |
|
||||
| Parent entry point | `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78`: dispatch запускается только из request; `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–41` вызывает только status; `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:23–25` регистрирует service, экспортирует scheduler, но не запускает request | Production consumer request не найден; DI registration не является вызовом |
|
||||
| Реальный enrichment producer | `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:122–129` → `backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45` → `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:234–241,269–289,113–125,189–225,497–577` | Это другой, живой producer: `quote(false)` → `section` → `readMany(false)` → при missing/stale `requestMany` → drain → publishSource |
|
||||
|
||||
В таблице «тот же файл» означает указанный выше полный path discovery service.
|
||||
Поиск ссылок `CatalogDiscoveryService`/`discovery.refresh` и
|
||||
`TbankSyncService` в `backend/src` не обнаружил другого producer entry point.
|
||||
Прямые вызовы `read(false)`/`request` существуют в
|
||||
`backend/test/tbank-catalog-discovery.e2e-spec.ts:86,184–187,202–203`; они
|
||||
подтверждают test reachability, но не production reachability. Удаление
|
||||
неживой producer-ветки и точные границы compatibility — предмет audit 04.
|
||||
|
||||
### Production callers `useScreenSync`: return не используется
|
||||
|
||||
| Call site | Фактический consumer |
|
||||
| --- | --- |
|
||||
| `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84` | Standalone вызов для пяти resources; return отброшен |
|
||||
| `frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:23` | Standalone вызов для accounts; return отброшен |
|
||||
| `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:67–75` | Standalone вызов для events; return отброшен |
|
||||
| `frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:66–69` | Standalone вызов для analytics; return отброшен |
|
||||
| `frontend/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts:96–99` | Standalone вызов для operations; return отброшен |
|
||||
| `frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:45–55` | Standalone вызов для stock sections; return отброшен |
|
||||
| `frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:45–55` | Standalone вызов для bond sections; return отброшен |
|
||||
| `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:13` | Standalone вызов для search result; return отброшен |
|
||||
|
||||
Только test harness сохраняет return и вызывает retry/refresh:
|
||||
`frontend/src/features/tbank-connection/model/screen-sync.test.tsx:37–50,68–78`.
|
||||
Реальный production side effect hook — timer
|
||||
`frontend/src/features/tbank-connection/model/screen-sync.ts:22–35`, не его
|
||||
return `:53–61`. Timer effect имеет `[]`: его clock начинается при mount,
|
||||
не перезапускается из-за resource array change; callback читает актуальный ref.
|
||||
|
||||
**Вне охвата:** полный clone detector по всему tree, generated/vendor/proto
|
||||
duplication, зависимости как предмет отдельного аудита, dead code (04),
|
||||
крупные module boundaries (01), эффективность API (07), качество tests (08),
|
||||
test infrastructure/factories (09), docs/SDD duplication (15). Root release/CI
|
||||
и похожие configs самостоятельных repos не объявлены DRY-долгом: они не
|
||||
должны становиться общей обязательной tooling-зависимостью.
|
||||
|
||||
### Knowledge Duplication Register
|
||||
|
||||
| Rule/behavior | Copies | Divergence risk | Single owner? | Extraction trade-off | Decision |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Password 12–72 UTF-8 bytes | FE `auth.schema.ts:10–11,36–43`; BE `auth.dto.ts:16–17,43–74`; snapshot `:5246–5250` | Нижняя граница уже разошлась по единицам | Да: auth policy; server владеет enforcement, spec фиксирует требование | Согласовать две независимые проверки; TS generator не реализует byte validation | FIX / DRY-001 |
|
||||
| Detail instrument whitelist | `catalog-discovery.service.ts:499–531`; `catalog-enrichment.service.ts:646–678` | Новый field/filter требует двух правок | Да: instruments boundary | Один локальный mapper; не объединять online и durable execution | SIMPLIFY / DRY-002 |
|
||||
| Runtime payment systems формы | FE `form.schema.ts:65–69`; `BankCardFields.vm.ts:19–22`; BE tuple `bank-card.dto.ts:45` | FE schema и selectable options могут расходиться | Да внутри FE bank-card; BE отдельно владеет wire vocabulary | Tuple у существующей schema; generated union проверяет допустимость, но не даёт runtime list | SIMPLIFY / DRY-003 |
|
||||
| Четыре access-loss codes | FE `client.ts:162–169`; `resource-query.ts:42–51`; `AccountPage.vm.ts:59–64` | Добавление code может изменить retry, purge и page state по-разному | Да: API code taxonomy; actions имеют разных owners | Один predicate, сохранить отдельные действия и дополнительный auth code | SIMPLIFY / DRY-004 |
|
||||
| Independent banking validation | FE `form.schema.ts:8–42`, `OperationForm.schema.ts:53–131`; BE `rub-money.ts:12–30`, `bank-operations.service.ts:94–210` | Значения policy требуют согласования; implementations специально различаются | Server — authority, browser — UX snapshot | Shared runtime validator не заменит ownership/CAS/transaction и ухудшит repo autonomy | KEEP / DRY-005 |
|
||||
| Dividend/coupon forecast и UI | BE stock `stock-income.service.ts:114–178`, bond `bond-income.service.ts:145–201`; FE payout views | Опасность скрыть payment/record/date и per-share/per-bond differences | Нет для всего процесса; общие primitives уже имеют owners | Generic payout с mode/flags объединит разные причины изменения | KEEP / DRY-006 |
|
||||
| Stock/Bond query declarations | FE `stock/api/queries.ts:21–95`, `bond/api/queries.ts:21–104` | Малое локальное повторение, разные endpoints/accessGroup/contracts | Два entity owners; transport общий | Оставить endpoint-specific generated options, не строить string-templated query factory | KEEP / DRY-006 |
|
||||
| Query wrapper / Mantine primitives | `authenticated-query.ts:34–59`, `resource-query.ts:37–67,84–149`; UI packages | Удаление defaults потеряет session/access/resource/ARIA поведение | Да для каждой существующей focused boundary | Нативные controls плюс project defaults уже используются; passthrough переименование не предлагается | KEEP / DRY-007 |
|
||||
| OpenAPI → snapshot → TS aliases | BE DTO/Swagger, FE snapshot/generated, `bank-card/api/types.ts`, `auth-context.ts` | Freshness artifact остаётся отдельным вопросом | Backend editable source; FE локальный consumer artifact | Сохранить offline generation, не создавать shared DTO package/runtime validator | KEEP / DRY-008 |
|
||||
| Known UI errors vs open wire string | FE `problem-details.ts:1–28`; generated `schema.d.ts:731–747` | Unknown code должен сохранять fallback | Server code owners и FE known subset имеют разные задачи | Не превращать UI enum в закрытый весь wire contract | KEEP / DRY-008 |
|
||||
| Живой enrichment vs похожий discovery producer | BE enrichment `:113–125,189–225,497–593`; discovery `:66–70,84–86,149–166,227–267,340–495` | Generic extraction из неживой ветки добавит framework; изменение enrichment может потерять batching/fences | Enrichment владеет текущим producer; общая repository fence уже есть | Сохранить только используемые enrichment guarantees; discovery reachability/deletion — audit 04, не KEEP | KEEP / DRY-009 (узкий scope) |
|
||||
| Periodic reads и freshness triggers | FE `screen-sync.ts:22–35`; восемь standalone page calls; `App.tsx:13–20`; entity stale times | Триггеры распределены; return/retry не используются, runtime waste не измерен | Scheduling owner требует решения; freshness не равно interval | Query-owned interval требует проверки mount phase/latest-resource semantics, не гипотетической общей кнопки | INVESTIGATE / DRY-010 |
|
||||
|
||||
В таблице короткие имена относятся к точным полным paths из findings ниже.
|
||||
Скопированные generated значения не посчитаны независимо editable знаниями.
|
||||
|
||||
### Intentional Duplication to Keep
|
||||
|
||||
| Место / evidence | Runtime consumer | Почему повторение оправдано | Что нельзя потерять |
|
||||
| --- | --- | --- | --- |
|
||||
| `frontend/src/entities/bank-card/model/money.ts:4–23` и `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–30` | Form draft → normalized request → server application input | UI допускает пробелы/запятую/leading zeros; server принимает канонический RUB decimal и проверяет storage range | bigint precision, positive checks, ошибки вместо округления |
|
||||
| `frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:53–131` и `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–210` | Draft validation и create/update operation | UI проверяет cached список; server повторно читает authoritative user-owned records в transaction | Cross-user isolation, refund CAS/limit, rollback, replay |
|
||||
| `frontend/src/entities/stock/api/queries.ts:21–95`; `frontend/src/entities/bond/api/queries.ts:21–104`; `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:114–178`; `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:145–201` | Stock/Bond route и HTTP resource | Сходная композиция, разные payloads, null/date rules, quote units; общие chart/position/layout уже переиспользуются | Не смешать проценты номинала и Money, registry и payment date |
|
||||
| `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–225,497–577`; `backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179` | Живой quote(false) → section → readMany(false) → enrichment producer → publishSource | Local enrichment использует batching/observers, repository уже разделяет authorization/publication checks; generic queue из сходства с неживым discovery не нужна | Authorized producer, private work handles, conditional publication только используемого enrichment; discovery producer не входит в KEEP |
|
||||
| `backend/src/common/money.types.ts:1`; `backend/src/common/money.dto.ts:3–10`; `frontend/src/shared/api/generated/schema.d.ts:763–795` | Swagger/ValidationPipe и typed API client | Runtime metadata и независимые репозитории требуют разных форм представления одного контракта | Offline frontend input, open unknown errors, отсутствие Swagger в pure domain |
|
||||
|
||||
Эта выборка показывает несколько разных оснований KEEP, а не исключения,
|
||||
добавленные после обязательного требования «всё объединить».
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### DRY-001 — Password policy расходится по единицам, а не по месту проверки
|
||||
|
||||
- **Decision:** FIX.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/features/auth/model/auth.schema.ts:10–11,36–43`;
|
||||
`backend/src/modules/auth/presentation/auth.dto.ts:16–17,43–74`;
|
||||
`backend/src/main.ts:31–39`;
|
||||
`specs/openspec/specs/user-authentication/spec.md:9–10,109–110`.
|
||||
Installed semantics независимо проверены в
|
||||
`backend/node_modules/class-validator/cjs/decorator/string/MinLength.js:14–29`
|
||||
и `backend/node_modules/validator/lib/isLength.js:23–26`.
|
||||
- **Claim:** две проверки одного принятого правила имеют несовместимый
|
||||
минимум: frontend — bytes, backend — character length.
|
||||
- **Current mechanism:** browser считает обе границы TextEncoder-ом;
|
||||
`CredentialsDto` использует `MinLength(12)` плюс только upper-byte validator.
|
||||
Например, `абвгде` — шесть кириллических символов / двенадцать UTF-8 байтов:
|
||||
проходит FE правило 12–72, не проходит BE минимум 12 characters. Это вывод
|
||||
по коду и encoding, не результат выполненного HTTP-теста.
|
||||
- **Runtime consumer:** `frontend/src/features/auth/ui/AuthForm/AuthForm.tsx:24–40`
|
||||
выбирает обе schemas; provider отправляет password без изменения
|
||||
(`frontend/src/features/auth/model/auth.vm.ts:14–24,121–132`). Register/login
|
||||
принимают тот же DTO (`backend/src/modules/auth/presentation/auth.controller.ts:55–79`),
|
||||
далее password поступает в bcrypt (`backend/src/modules/auth/application/auth.service.ts:35–78`).
|
||||
- **Impact / actual cost:** UI принимает draft, API отказывает; AuthForm
|
||||
показывает общий requestError вместо успешного auth. Impact unmeasured:
|
||||
частота таких паролей и пользовательских отказов неизвестна.
|
||||
- **Why now:** уже доказан drift единицы policy; изменение локально у auth
|
||||
owner и не требует нового validator framework.
|
||||
- **Minimum change:** обе byte-границы проверять существующим
|
||||
`PasswordBytesConstraint`, убрать несовместимый character minimum.
|
||||
Согласовать Swagger описание bytes: snapshot сейчас содержит character
|
||||
`minLength: 12` (`frontend/src/shared/api/spec/openapi.json:5246–5250`), что
|
||||
не описывает byte-policy. При реализации явно исправить metadata и передать
|
||||
обновлённый artifact потребителю, не править generated TS вручную.
|
||||
- **Guarantees at risk:** server-side validation, bcrypt 72-byte ceiling,
|
||||
отсутствие trim/normalization password, совместимость login и error pointers.
|
||||
- **Counterarguments / trade-offs:** character minimum может быть отдельным
|
||||
security/product решением, но такого изменения не подтверждают текущая spec
|
||||
и docs (`docs/domains/authentication/overview.md:14–21`). Independent
|
||||
validation нужна; общего исполняемого пакета между repos не нужно.
|
||||
- **Verification or rollback:** будущая минимальная проверка — frontend schema
|
||||
и configured HTTP DTO boundary на 11/12/72/73 bytes, ASCII/кириллице/emoji,
|
||||
password whitespace без изменения. Existing DTO checks находятся в
|
||||
`backend/src/common/dto-validation.spec.ts:13–30`; HTTP flow должен сохранить
|
||||
400/VALIDATION_ERROR и success auth. Не менять stored hashes; rollback —
|
||||
возврат DTO/metadata/artifact совместно. В этом аудите проверки не запускались.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** DRY-005, DRY-008.
|
||||
|
||||
### DRY-002 — Одна instrument detail projection имеет две идентичные копии
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–531`;
|
||||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–678`.
|
||||
- **Claim:** `publicInstrument` копирует один и тот же allowlist для одного
|
||||
`TbankBond | TbankShare` input, включая одинаковый brand.logoName whitelist.
|
||||
- **Current mechanism:** две чистые функции без side effects или различающейся
|
||||
error/null политики. Обе сохраняют optional/undefined fields; отсутствие
|
||||
instrument обрабатывают callers, а не функция.
|
||||
- **Runtime consumer:** online detail/search —
|
||||
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:143–165,295–325`;
|
||||
company online и durable detail/bond-current load —
|
||||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:241–267,339–350`.
|
||||
Ответы преобразуют
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts:59–89`
|
||||
и `backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:58–88`;
|
||||
сохранение проходит через
|
||||
`backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–223,252–276`.
|
||||
- **Impact / actual cost:** изменение разрешённого field требует двух правок;
|
||||
drift может сделать online и saved representation разными. Текущего drift
|
||||
этих функций нет. Impact unmeasured; экономия maintenance/cognitive load.
|
||||
- **Why now:** существует реальный reuse внутри одного module и нет
|
||||
необходимости менять execution model ради него.
|
||||
- **Minimum change:** оставить одну чистую функцию у instruments boundary,
|
||||
например `instruments/infrastructure/public-instrument.mapper.ts`, и два
|
||||
локальных imports. Не создавать Nest provider, generic mapper или общий
|
||||
cross-repo package.
|
||||
- **Ownership model:** этот module-owned mapper вызывают discovery и
|
||||
enrichment application services; он зависит только от integration record
|
||||
types. Application → infrastructure уже используется этими владельцами;
|
||||
новых cross-module exports или forbidden imports не требуется.
|
||||
- **Guarantees at risk:** explicit allowlist, отсутствие private quantities
|
||||
и owner fields, nested brand whitelist, UID check, null/undefined semantics,
|
||||
publication fences и оригинальное fetchedAt.
|
||||
- **Counterarguments / trade-offs:** online и saved execution действительно
|
||||
различаются. Общая projection не должна объединять их orchestration. Skinny
|
||||
discovery payload
|
||||
`backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:358–387`
|
||||
имеет другие обязанности и
|
||||
`update: {}`; это другой shape, не цель reuse полной detail projection.
|
||||
Reachability/deletion этого discovery publication path относится к audit 04;
|
||||
DRY-002 не требует сохранять его или discovery producer.
|
||||
- **Verification or rollback:** future check одного share и bond с extra
|
||||
private fields, unknown/null optional fields и лишним brand field через
|
||||
обе paths; сохранить UID mismatch guard и CAS. Existing discovery assertions
|
||||
`backend/test/tbank-catalog-discovery.e2e-spec.ts:119–177,197–229` защищают
|
||||
близкую boundary через direct service calls, но не доказывают production
|
||||
reachability discovery producer или полный online/enrichment parity.
|
||||
Rollback — вернуть два локальных вызова/definitions без schema migration.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-006, DRY-009.
|
||||
|
||||
### DRY-003 — Select options и Zod enum дублируют словарь одной bank-card формы
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/entities/bank-card/model/form.schema.ts:65–69`;
|
||||
`frontend/src/entities/bank-card/ui/BankCardForm/BankCardFields.vm.ts:19–25`;
|
||||
`backend/src/modules/banking/bank-cards/presentation/bank-card.dto.ts:45,80–83,245–246`;
|
||||
`frontend/src/shared/api/generated/schema.d.ts:767–795`.
|
||||
- **Claim:** в frontend одну runtime vocabulary вручную поддерживают и schema,
|
||||
и selectable options. Здесь одинаковы owner, причина изменения и semantics.
|
||||
- **Current mechanism:** schema содержит `""` и три payment-system значения;
|
||||
fields VM независимо содержит те же три значения. `""` — UI sentinel,
|
||||
преобразуемый в `null`, а не четвёртая платёжная система.
|
||||
- **Runtime consumer:** fields VM передаёт options форме; form VM использует
|
||||
schema и отправляет `values.paymentSystem || null`
|
||||
(`frontend/src/entities/bank-card/ui/BankCardForm/BankCardForm.vm.ts:30–39,50–65`).
|
||||
- **Impact / actual cost:** при изменении словаря можно получить selectable,
|
||||
но невалидное значение или валидное, но недоступное для выбора. Сегодня
|
||||
списки совпадают. Impact unmeasured; малая maintenance cost.
|
||||
- **Why now:** один локальный export может удалить вторую editable копию без
|
||||
новых layers, dependencies или API change.
|
||||
- **Minimum change:** существующая `form.schema.ts` владеет typed const tuple
|
||||
реальных payment systems; Zod добавляет пустой sentinel, VM строит labels
|
||||
из этого tuple. Проверять tuple через generated `BankCard`/request union
|
||||
аналогично existing `model/operation-kinds.ts:1–8`.
|
||||
- **Ownership model:** schema и fields VM остаются в `entities/bank-card`;
|
||||
VM импортирует local schema export. Не нужен `shared/constants`, новый slice
|
||||
или import backend checkout. `satisfies` проверяет допустимость values,
|
||||
но не является доказательством exhaustive покрытия future enum.
|
||||
- **Guarantees at risk:** clearable Select, `null` в API, `""` в form draft,
|
||||
сохранение выбранного значения, перевод labels и independent server checks.
|
||||
- **Counterarguments / trade-offs:** словарь маленький и меняется редко;
|
||||
это optional cleanup при работе над формой. Generated TS union не является
|
||||
runtime list; вводить второй generator ради этих трёх values невыгодно.
|
||||
- **Verification or rollback:** existing visible selection/save case
|
||||
`frontend/src/entities/bank-card/ui/BankCardForm/BankCardForm.test.tsx:86–105`,
|
||||
clearing-to-null `:55–84`; при реализации проверить каждый option и empty
|
||||
sentinel, затем local type/dependency checks. Rollback локальный, API прежний.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-005, DRY-008.
|
||||
|
||||
### DRY-004 — Access-loss taxonomy повторяется, recovery actions объединять не надо
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/shared/api/resource-query.ts:37–67`;
|
||||
`frontend/src/shared/api/client.ts:162–179`;
|
||||
`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:59–64,86–96`.
|
||||
- **Claim:** один набор `FORBIDDEN`, `INVALID_TBANK_TOKEN`, `NOT_FOUND`,
|
||||
`TBANK_NOT_CONNECTED` независимо перечислен для resource loss, permanent
|
||||
transport failure и блокирующего account state.
|
||||
- **Current mechanism:** resource отменяет/очищает access group; transport
|
||||
запрещает retry и отдельно включает `UNAUTHORIZED`; Account VM выбирает
|
||||
error вместо snapshot. Совпадает membership классификации, не actions.
|
||||
- **Runtime consumer:** `useResourceQuery` применяет classifier в effect
|
||||
(`frontend/src/shared/api/resource-query.ts:99–102`); `fetchAppRead` вызывает
|
||||
transient classifier (`frontend/src/shared/api/client.ts:127–150,172–179`);
|
||||
`frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:9–12` использует VM state.
|
||||
- **Impact / actual cost:** новая access-loss причина потребует синхронно
|
||||
изменить три места, иначе retry/cache/page state могут разойтись. Сейчас
|
||||
набор согласован. Impact unmeasured; maintenance risk, не доказанный дефект.
|
||||
- **Why now:** existing owner `shared/api/problem-details.ts` уже владеет
|
||||
известными codes и unknown fallback; классификацию можно расширить там.
|
||||
- **Minimum change:** один predicate именно для этих четырёх codes у этого
|
||||
owner; transport добавляет свой `UNAUTHORIZED`, resource и Account VM
|
||||
сохраняют прежние actions. Не создавать error registry или общий UI handler.
|
||||
- **Ownership model:** `client.ts`/`resource-query.ts` используют local import;
|
||||
Account VM — public `@/shared/api`. Никаких dependencies из shared в
|
||||
features/entities/pages. Это API error taxonomy, не банковский расчёт.
|
||||
- **Guarantees at risk:** unknown fallback, auth refresh отдельно от broker
|
||||
access, cached data при temporary failures и clearing после confirmed loss.
|
||||
- **Counterarguments / trade-offs:**
|
||||
`frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:16–19` намеренно
|
||||
имеет subset из двух connection codes; расширять его до четырёх нельзя.
|
||||
Error views имеют разные settings/back actions
|
||||
(`frontend/src/pages/account/ui/AccountPage/components/AccountPageError.view.tsx:15–43`,
|
||||
`frontend/src/pages/accounts/ui/AccountsPage/components/AccountsPageError.view.tsx:22–38`);
|
||||
эти branches остаются у pages. Если название helper скрывает это различие,
|
||||
оставить текущие короткие списки дешевле плохого generic handler.
|
||||
- **Verification or rollback:** future MSW checks для membership four codes,
|
||||
special `UNAUTHORIZED`, `TBANK_UNAVAILABLE`, unknown/missing code; разные
|
||||
actions остаются. Existing boundary tests:
|
||||
`frontend/src/shared/api/resource-query.test.tsx:174–209`,
|
||||
`frontend/src/shared/api/client.test.ts:261–281`. Rollback — вернуть predicates,
|
||||
не менять error wire values или response shapes.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-007, DRY-008.
|
||||
|
||||
### DRY-005 — Banking validation и точные money representations оставить на обеих границах
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/entities/bank-card/model/money.ts:4–23`;
|
||||
`frontend/src/entities/bank-card/model/form.schema.ts:8–42`;
|
||||
`frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:53–131`;
|
||||
`backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`;
|
||||
`backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–210`.
|
||||
- **Claim:** повторение validation защищает разные trust boundaries; money
|
||||
helpers также не являются одинаковым public interface.
|
||||
- **Current mechanism:** UI draft понимает comma/trim/leading zeros, возвращает
|
||||
null/field errors и проверяет cached данные. Server требует canonical RUB
|
||||
decimal, storage range и positive operation amount; затем authoritative
|
||||
records, ownership и refunds проверяются в write transaction.
|
||||
- **Runtime consumer:** BankCardForm и OperationForm → typed mutation →
|
||||
DTO/input → `createCard`/`createOperation`. Server write boundary:
|
||||
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–59`;
|
||||
replay fingerprint и ownership:
|
||||
`backend/src/modules/banking/bank-cards/application/bank-cards.service.ts:74–95`,
|
||||
`backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:214–239`.
|
||||
- **Impact / actual cost:** дополнительное сопровождение policy values есть,
|
||||
но удаление server validation позволяет обойти browser checks, а reuse
|
||||
UI normalizer изменит server semantics. Impact unmeasured.
|
||||
- **Why now:** DRY-001 показывает риск расхождения значений, но не оправдывает
|
||||
универсальный shared validator или замену bigint на floating point.
|
||||
- **Minimum change:** сохранить независимые implementations; согласовывать
|
||||
конкретные policy values и примеры при изменении границы. Existing
|
||||
`backend/src/common/decimal.ts:19–83` уже обслуживает server arithmetic;
|
||||
`backend/src/integrations/tbank/decimal.ts:7–13` занимается protobuf units/nano
|
||||
отдельно.
|
||||
- **Guarantees at risk:** signed-64-bit bounds input, precision сумм сверх
|
||||
storage range, positive/zero distinction, user ownership, CAS, transaction
|
||||
rollback и replay protection.
|
||||
- **Counterarguments / trade-offs:** параллельные constants требуют parity
|
||||
review; frontend/backend не должны делить исполняемые business modules.
|
||||
`rubMoney` форматирует вычисленную сумму и не обязан ограничивать её input
|
||||
storage range, в отличие от `parseRubMoney`.
|
||||
- **Verification or rollback:** future changes проверять existing boundary
|
||||
cases `frontend/src/entities/bank-card/model/form-validation.test.ts:13–48`
|
||||
и `backend/src/modules/banking/bank-cards/domain/rub-money.spec.ts:4–56`.
|
||||
Финансовые changes дополнительно требуют реальной изолированной transaction
|
||||
proof. В аудите ничего не удалено, rollback не требуется.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-001, DRY-003, DRY-008.
|
||||
|
||||
### DRY-006 — Stock/Bond сходство не оправдывает generic page/query/payout pipeline
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:23–98`;
|
||||
`frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–98`;
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:114–178`;
|
||||
`backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:145–201`.
|
||||
- **Claim:** одинаковая композиция закрывает разные business semantics;
|
||||
действительно общие primitives уже переиспользуются.
|
||||
- **Current mechanism:** dividends фильтруют cancelled и выбирают timing по
|
||||
paymentDate с recordDate fallback; coupons несут number/type/periodDays и
|
||||
timing по paymentDate. Quotes stock — Money, bond — percent of nominal,
|
||||
clean/dirty/ACI. Страницы используют общий instrument-details layout,
|
||||
section, chart, position и operation rows, но разные company/metrics/payouts.
|
||||
- **Runtime consumer:** route containers
|
||||
`frontend/src/pages/stock/ui/StockPage/StockPage.tsx:10–21` и
|
||||
`frontend/src/pages/bond/ui/BondPage/BondPage.tsx:10–21`;
|
||||
reused composition —
|
||||
`frontend/src/pages/stock/ui/StockPage/StockPage.view.tsx:38–72`,
|
||||
`frontend/src/pages/bond/ui/BondPage/BondPage.view.tsx:134–205`.
|
||||
HTTP consumers —
|
||||
`backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:170–181`,
|
||||
`backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:126–134`.
|
||||
- **Impact / actual cost:** текущая локальная duplication проще universal
|
||||
hook с динамическим endpoint, type casts и flags для income kind/company/
|
||||
metrics/price unit. Impact unmeasured; savings от такой extraction не доказаны.
|
||||
- **Why now:** защищает от ложного DRY на основании похожего JSX и одинакового
|
||||
UUID regex. У entity hooks разные wire contracts и accessGroup usage.
|
||||
- **Minimum change:** сохранить endpoint-specific entity query declarations
|
||||
и page composition. Existing `features/instrument-details` остаётся общим
|
||||
владельцем focused presentation, без объединения stock/bond entities.
|
||||
- **Guarantees at risk:** exact decimal currency/percent semantics, unknown
|
||||
amount vs zero, registry/payment dates, cancelled dividends, enabled inputs,
|
||||
stable generated query keys и отсутствие запрещённых sibling imports.
|
||||
- **Counterarguments / trade-offs:** horizon constants и часть grouping похожи;
|
||||
отдельное маленькое правило может заслужить reuse при реальном совместном
|
||||
изменении. Это не основание сейчас переносить весь orchestration или делать
|
||||
boolean-driven payout component.
|
||||
- **Verification or rollback:** future common changes должны сохранять
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock.spec.ts:10–37,45–69`,
|
||||
`backend/src/modules/investments/instruments/bonds/application/bond.spec.ts:27–113,139–176`
|
||||
и видимые stock/bond
|
||||
payout fields. Local import boundaries проверяет
|
||||
`frontend/dependency-cruiser.cjs:42–64`. В аудите изменения нет.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-002, DRY-007, DRY-010.
|
||||
|
||||
### DRY-007 — Query wrappers и UI primitives несут project responsibility
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/shared/api/authenticated-query.ts:34–59`;
|
||||
`frontend/src/shared/api/resource-query.ts:37–67,84–149`;
|
||||
`frontend/packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx:5–17`;
|
||||
`frontend/packages/ui/src/StateView/StateView.tsx:5–27`.
|
||||
- **Claim:** эти wrappers не просто воспроизводят API библиотеки под новым
|
||||
названием; они задают конкретные session/resource/ARIA/presentation defaults.
|
||||
- **Current mechanism:** authenticated query совмещает session и caller
|
||||
enabled; resource wrapper очищает access group, проецирует envelope data/meta,
|
||||
сохраняет transport status и refetch envelope. LoadingSkeleton задаёт busy
|
||||
status/label, StateView — единый alert/layout; EntryCard — общую композицию,
|
||||
tone и действия (`frontend/packages/ui/src/EntryCard/EntryCard.tsx:24–81`).
|
||||
- **Runtime consumer:** portfolio query
|
||||
`frontend/src/entities/portfolio/api/usePortfolio.ts:19–25`, banking query
|
||||
`frontend/src/entities/bank-card/api/queries.ts:28–44`; instrument sections
|
||||
используют LoadingSkeleton
|
||||
(`frontend/src/features/instrument-details/ui/InstrumentSection.view.tsx:43–54`),
|
||||
Account/Accounts error views используют StateView.
|
||||
- **Impact / actual cost:** удаление wrappers размножит session gates,
|
||||
resource access handling и accessibility defaults по callers. Impact
|
||||
unmeasured; независимая стоимость текущих abstractions не измерялась.
|
||||
- **Why now:** ponytail ladder о library/native API не означает удаления
|
||||
useful project defaults. Mantine controls уже используются напрямую там,
|
||||
где нет дополнительной ответственности
|
||||
(`frontend/src/features/auth/ui/AuthForm/AuthForm.tsx:49–83`).
|
||||
- **Minimum change:** сохранить focused wrappers и primitives. Не добавлять
|
||||
собственные Button/Select passthrough wrappers. DRY-004 может упростить
|
||||
классификацию внутри existing owner, не заменить whole wrapper.
|
||||
- **Guarantees at risk:** no fetch before session, combining enabled, unknown
|
||||
resource при успешном HTTP, access-loss cache clearing, сохранение cached
|
||||
snapshot и доступные loading/error states.
|
||||
- **Counterarguments / trade-offs:** wrapper вручную выбирает часть Query
|
||||
return API; при новом consumer нужно расширять осмысленно. Observation
|
||||
compatibility может быть отдельным dead-code вопросом audit 04, но её
|
||||
возможная ненужность не доказывает ненужность wrapper целиком.
|
||||
- **Verification or rollback:** existing resource boundary assertions
|
||||
`frontend/src/shared/api/resource-query.test.tsx:72–138,174–209`
|
||||
различают transport success,
|
||||
projection и clearing. Future UI changes сохраняют keyboard/ARIA basics;
|
||||
tests приложения не заменяют browser proof. В аудите изменений нет.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-004, DRY-006, DRY-010.
|
||||
|
||||
### DRY-008 — Generated contracts и known-error subset не являются competing DTO truths
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/openapi.ts:4–10`;
|
||||
`backend/.ai/conventions/http-api.md:69–81`;
|
||||
`frontend/package.json:22–23`;
|
||||
`frontend/src/entities/bank-card/api/types.ts:1–34`;
|
||||
`frontend/src/features/auth/model/auth-context.ts:14–15`;
|
||||
`frontend/src/shared/api/generated/schema.d.ts:731–795`.
|
||||
- **Claim:** reviewed transport types выводятся из provided snapshot; не
|
||||
найдена независимая handwritten DTO-копия для рассмотренных bank/auth flows.
|
||||
- **Current mechanism:** editable contract — BE Nest DTO/Swagger; provided
|
||||
snapshot — вход FE offline generation; aliases используют `paths`.
|
||||
Form values выводятся из schemas. `MoneyDto implements Money`
|
||||
(`backend/src/common/money.dto.ts:3–10`) сохраняет runtime Swagger metadata
|
||||
отдельно от pure `backend/src/common/money.types.ts:1`.
|
||||
- **Runtime consumer:** `frontend/src/shared/api/client.ts:1–6` импортирует
|
||||
generated paths; form/auth/bank query callers используют derived aliases.
|
||||
Known UI enum `frontend/src/shared/api/problem-details.ts:1–28` обслуживает
|
||||
recovery, в то время как
|
||||
generated ProblemDetails.code остаётся открытой строкой.
|
||||
- **Impact / actual cost:** artifact freshness требует сопровождения, но
|
||||
удаление snapshots или общий исполняемый DTO package нарушит independent
|
||||
repo inputs. Impact unmeasured; текущего file-to-file drift OpenAPI нет.
|
||||
- **Why now:** наличие нескольких файлов с одной shape не означает несколько
|
||||
редактируемых источников значения. DRY-001 и DRY-003 касаются runtime policies,
|
||||
которые TypeScript declarations не реализуют автоматически.
|
||||
- **Minimum change:** оставить существующий contract flow и local aliases;
|
||||
согласовывать metadata при DRY-001. Сохранить runtime known subset ошибок с
|
||||
fallback, не закрывать wire string ради auto-generated exhaustive enum.
|
||||
- **Guarantees at risk:** compatible wire contract, offline FE generation,
|
||||
независимость CI/checkout, unknown-code fallback, domain без Swagger.
|
||||
- **Counterarguments / trade-offs:** равенство existing OpenAPI файлов не
|
||||
подтверждает runtime freshness, а typed `satisfies` не проверяет все future
|
||||
enum values. Это ограничение существующего flow, не доказательство нужности
|
||||
ещё одного hash/validator registry. Fresh generation parity — UNVERIFIED.
|
||||
- **Verification or rollback:** при будущих API changes — предусмотренные
|
||||
backend contract checks и frontend offline `openapi:check`, без чтения
|
||||
соседнего source как обязательного входа. В аудите выполнено только сравнение
|
||||
existing artifacts; generation/build не запускались.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-001, DRY-003, DRY-004, DRY-005.
|
||||
|
||||
### DRY-009 — Живой enrichment/shared fence: не вводить generic queue из сходства с discovery
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–225,497–577`;
|
||||
`backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179,280–318`;
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45`.
|
||||
Reachability counterexample:
|
||||
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:66–70,84–93,149–190,227–267`;
|
||||
`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78,192–201`;
|
||||
`backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–41`.
|
||||
- **Claim:** KEEP касается используемых enrichment batching/observer/publication
|
||||
guarantees и общей repository fence. Сходство с discovery не оправдывает
|
||||
generic queue и не является основанием сохранять discovery producer.
|
||||
- **Current mechanism:** enrichment `requestMany` при missing/stale sources
|
||||
резервирует work и ставит drain; price batch ограничен одной user/revision,
|
||||
поздние observers используют `publishSource` с initiating attempt. Repository
|
||||
отдельно claim-ит initiator и observer. Discovery default HTTP branches online,
|
||||
observe branches read-only; `read(true)` пропускает request.
|
||||
- **Runtime consumer:** `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:122–129`
|
||||
→ `backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45`
|
||||
→ enrichment `section('quote', false)`
|
||||
(`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:234–241,269–289`)
|
||||
→ `readMany(false)` (`:113–125`)
|
||||
→ при missing/stale `requestMany` (`:189–225`) → drain/publishSource (`:497–577`).
|
||||
Bond quote имеет тот же живой entry point
|
||||
(`backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:92–99`;
|
||||
`backend/src/modules/investments/instruments/bonds/application/bond-market.service.ts:38–49`).
|
||||
Ни default catalog search/detail/byTicker, ни observe calls discovery producer
|
||||
не запускают. Единственный найденный внешний production-text refresh caller
|
||||
находится в manual dispatch без найденного production request consumer.
|
||||
- **Impact / actual cost:** generic queue ради устранения сходства с
|
||||
dead-parent-reachable кодом добавит callbacks/flags вместо устранения
|
||||
неиспользуемого пути. Изменение живого enrichment может потерять authorization
|
||||
и publication fences. Impact unmeasured; runtime savings не измерены.
|
||||
- **Why now:** исправлен ложный runtime consumer прежней редакции. Наличие
|
||||
definitions, DI registration и direct tests не доказывает producer reachability.
|
||||
- **Minimum change:** не вводить generic queue; сохранить гарантии живого
|
||||
enrichment и используемые им `reserve/publishSource/finish`. DRY-002 извлекает
|
||||
stateless projection независимо. Discovery `request/refresh/drain` не входит
|
||||
в KEEP; reachability/deletion и compatibility reads — предмет audit 04.
|
||||
- **Guarantees at risk:** connection revision/attempt identity, isolation
|
||||
initiating credentials, private observer handles, late-publication fences,
|
||||
same-owner batching и conditional finish именно живого enrichment.
|
||||
- **Counterarguments / trade-offs:** discovery producer в source действительно
|
||||
похож и исполняется из direct tests, но current catalog HTTP paths его
|
||||
обходят; это аргумент за отдельный dead-code разбор, не за сохранение двух
|
||||
queues. KEEP не распространяется на весь CatalogDiscoveryService/repository,
|
||||
unused methods или каждый completion helper enrichment. Равноценность общей
|
||||
queue и снижение total cost — UNVERIFIED.
|
||||
- **Verification or rollback:** при изменении живой boundary нужны controlled
|
||||
quote/enrichment scenarios с late replace/delete, invalid initiating token,
|
||||
batch/observer isolation и conditional publication. Direct discovery tests
|
||||
(`backend/test/tbank-catalog-discovery.e2e-spec.ts:184–187,202–203`) не являются
|
||||
proof текущего HTTP flow. Реализация в этом аудите не изменена; rollback
|
||||
source не требуется. Audit 04 определяет deletion scope отдельно.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DRY-002.
|
||||
|
||||
### DRY-010 — Scheduling ownership требует проверки, глобальная freshness policy преждевременна
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:20–35,53–61`;
|
||||
`frontend/src/app/ui/App.tsx:13–20`;
|
||||
`frontend/src/entities/portfolio/api/usePortfolio.ts:3–5,19–25`;
|
||||
`frontend/src/entities/brokerage-account/api/useBrokerageAccounts.ts:3–18`;
|
||||
`specs/openspec/specs/brokerage-accounts/spec.md:68,96–98`;
|
||||
`specs/openspec/specs/portfolio-view/spec.md:122–124`.
|
||||
- **Claim:** у одного HTTP-read flow есть page timer, query freshness и app
|
||||
refetch triggers. Возможность уменьшить собственную coordination реальна,
|
||||
но их полное объединение не является доказанным behavior-preserving cleanup.
|
||||
- **Current mechanism:** screen hook каждые 1 800 000 ms вызывает refetch
|
||||
текущего resource array; отдельные hooks задают 30 seconds для portfolio и
|
||||
30 minutes для accounts; app включает focus/reconnect. Spec специально
|
||||
различает freshness и periodic interval, а focus refetch запрещает.
|
||||
- **Runtime consumer:** Account VM передаёт пять resources
|
||||
(`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84`);
|
||||
Stock/Bond — свои section queries; BondsPage
|
||||
передаёт search result (`frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:10–13`).
|
||||
Все восемь production call sites перечислены в разделе 3: ни один не
|
||||
сохраняет/передаёт return hook. `refresh/retry` используются только test
|
||||
harness (`frontend/src/features/tbank-connection/model/screen-sync.test.tsx:37–50,68–78`),
|
||||
не кнопкой текущего production UI.
|
||||
- **Impact / actual cost:** maintenance scheduling policy распределено по
|
||||
owners; runtime число лишних GET, interval phase, background-tab behavior и
|
||||
эффект от native query-owned interval не измерены. Impact unmeasured.
|
||||
- **Why now:** важно не заменять все staleTime на один constant ради DRY.
|
||||
Неиспользуемый return/retry не является основанием сохранять timer;
|
||||
его reachability/deletion относится к audit 04.
|
||||
- **Minimum change:** сначала проверить семантику interval относительно
|
||||
последнего успешного read, focus/reconnect, disabled inputs и активных
|
||||
observers. Затем решить, достаточно ли существующих query options для
|
||||
periodic reads. До подтверждения не создавать global query factory или
|
||||
universal policy object.
|
||||
- **Guarantees at risk:** caller enabled/session gates, canonical cache key,
|
||||
cleanup при unmount, HTTP-only periodic read, сохранение snapshot и отсутствие
|
||||
hidden personal-import commands.
|
||||
- **Counterarguments / trade-offs:** существующий timer реально имеет общую
|
||||
mount-based фазу: effect с `[]` не перезапускается при смене resource array,
|
||||
а callback читает актуальный ref
|
||||
(`frontend/src/features/tbank-connection/model/screen-sync.ts:22–35`). Query-owned timers
|
||||
могут изменить фазу, поведение при смене inputs или background/observer
|
||||
semantics. Общая кнопка retry из return не является counterargument —
|
||||
production consumer не найден. Разные 30 sec/30 min в specs не противоречат
|
||||
друг другу. UNVERIFIED: equivalence замены, необходимость общего clock
|
||||
как требования и фактические savings.
|
||||
- **Verification or rollback:** дешевейшее подтверждение — controlled-time
|
||||
MSW сценарий существующего query/page owner, assert на видимые данные и GET
|
||||
при mount/focus/interval/unmount/disabled search, затем сверка native options
|
||||
со штатной документацией используемой версии. Никакой реальной сети и
|
||||
нагрузочного benchmark для первого решения не нужно. Future rollback —
|
||||
вернуть только scheduling options/timer, сохранив generated keys.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** DRY-006, DRY-007.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | IDs / область | Итог |
|
||||
| --- | --- | --- |
|
||||
| DELETE | — | Подтверждённых удалений в scope 03 нет; недоступные flows относятся к audit 04 |
|
||||
| SIMPLIFY | DRY-002 | Один module-local instrument projection mapper; whitelist сохраняется |
|
||||
| SIMPLIFY | DRY-003 | Один typed runtime tuple у текущей bank-card schema |
|
||||
| SIMPLIFY | DRY-004 | Один code-membership predicate у existing API error owner; actions остаются разными |
|
||||
| FIX | DRY-001 | Согласовать единицу password minimum и API metadata, сохранить две trust-boundary checks |
|
||||
| KEEP | DRY-005 | Money precision, server validation, transaction/CAS/refund/replay protections |
|
||||
| KEEP | DRY-006 | Stock/Bond contracts и business/render semantics; existing focused reuse достаточен |
|
||||
| KEEP | DRY-007 | Authenticated/resource wrappers и доступные UI primitives |
|
||||
| KEEP | DRY-008 | Provider-owned OpenAPI, consumer artifacts, derived aliases, open-code fallback |
|
||||
| KEEP | DRY-009 | Только гарантии живого enrichment и используемая repository fence; discovery producer — audit 04, не KEEP |
|
||||
| INVESTIGATE | DRY-010 | Scheduling simplification только после проверки эквивалентности |
|
||||
|
||||
### Minimal ownership model для reuse
|
||||
|
||||
| Правило | Единственная implementation / value | Callers | Запрещённое направление / лишняя abstraction |
|
||||
| --- | --- | --- | --- |
|
||||
| Detail projection / DRY-002 | Локальный `instruments/infrastructure/public-instrument.mapper.ts` | Discovery/enrichment application внутри instruments | Не в global common/utils, не integration → business DTO, не DI provider |
|
||||
| FE payment list / DRY-003 | Typed tuple в existing `entities/bank-card/model/form.schema.ts` | Zod enum и bank-card fields VM | Не frontend → backend checkout, не shared → entity, не новый generator |
|
||||
| Access-loss membership / DRY-004 | Predicate в existing `shared/api/problem-details.ts` | Local client/resource; Account VM через `shared/api/index.ts` | Не shared → features/pages, не generic recovery component |
|
||||
| Password policy / DRY-001 | BE CredentialsDto validator — enforcement; FE auth schema — UX implementation той же spec | Register/login boundaries | Не удалять server check, не shared cross-repo executable validator |
|
||||
| Shared instrument presentation / KEEP | Existing `features/instrument-details` | Stock/Bond pages | Не entity sibling import, не generic boolean-driven page |
|
||||
|
||||
Сравнение вариантов для всех `SIMPLIFY`:
|
||||
|
||||
- **Оставить копии:** допустимо для P3, если область не меняется; сохраняется
|
||||
несколько мест правки. Для DRY-002 benefit выше: одна visibility projection.
|
||||
- **Перенести к owner:** предпочтительно для DRY-002–004; число business
|
||||
owners не меняется, imports локальны или уже разрешены.
|
||||
- **Генерировать:** полезно для wire types и уже выполняется; byte validators,
|
||||
recovery actions и runtime UI lists нельзя получить из erased TS declarations
|
||||
автоматически. Дополнительный generator здесь дороже tuple/predicate.
|
||||
- **Сделать shared:** допустим только existing focused API code owner DRY-004.
|
||||
Межрепозиторный shared package и огромный `shared/utils` создадут release,
|
||||
build, trust-boundary и layer coupling без подтверждённой потребности.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| ID | Класс ожидаемой пользы | Фактическое измерение | Обязательная regression boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| DRY-001 | Reliability/UX и maintenance согласованной policy | not measured; несовместимость доказана по source | UTF-8 границы, HTTP validation, password без преобразования, published metadata |
|
||||
| DRY-002 | Cognitive load, maintenance; один visibility owner | not measured; две definitions подтверждены | Whitelist/nested brand, optional fields, online/saved parity, UID/CAS |
|
||||
| DRY-003 | Небольшая maintenance/cognitive экономия | not measured; две local lists подтверждены | Все options, empty sentinel и null request |
|
||||
| DRY-004 | Maintenance при изменении code taxonomy | not measured; три полных набора подтверждены | Unknown/temporary errors, auth special case, cache clearing и разные actions |
|
||||
| DRY-005–009 | Избежать лишнего coupling и удаления используемой защиты | not measured | Money precision, server authority, live enrichment publication/session boundaries, accessibility; discovery producer исключён |
|
||||
| DRY-010 | Возможная экономия собственных scheduling constructs | UNVERIFIED / not measured | Mount timer phase/latest-resource ref, enabled, active observers, cached snapshot, HTTP-only reads; unused return/retry не защищается |
|
||||
|
||||
Никаких claims об ускорении, CI minutes, bundle size или agent-token savings
|
||||
не сделано. Для выбранных cleanup нет подтверждённого кандидата на удаление
|
||||
dependency. Net LOC не вычислялся: нет implementation diff, а helper imports,
|
||||
public exports и необходимые regression checks также стоят кода.
|
||||
|
||||
### Complexity-only pass в формате ponytail-audit
|
||||
|
||||
- `shrink:` убрать вторую `publicInstrument`; одна instruments boundary projection. [DRY-002; backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–531; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–678]
|
||||
- `shrink:` убрать повторные полные access-loss списки; один predicate у existing API code owner, actions отдельно. [DRY-004; frontend/src/shared/api/client.ts:162–169; frontend/src/shared/api/resource-query.ts:42–51; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:59–64]
|
||||
- `shrink:` убрать вторую runtime payment-system list формы; typed tuple existing schema. [DRY-003; frontend/src/entities/bank-card/model/form.schema.ts:65–69; frontend/src/entities/bank-card/ui/BankCardForm/BankCardFields.vm.ts:19–22]
|
||||
|
||||
`net: -N lines, -0 deps possible.` Здесь `N = not measured`: сокращение строк
|
||||
без подготовленного и проверенного diff не оценено; 0 относится к предложенным
|
||||
удалениям deps, не к исчерпывающему dependency audit. DRY-001 и защищаемые
|
||||
инварианты не включены в complexity-only cuts.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
Все четыре шага ниже подтверждены source analysis; ранжирование учитывает
|
||||
value/risk/effort, а не число одинаковых строк.
|
||||
|
||||
### Требует behavior/contract подтверждения
|
||||
|
||||
1. **DRY-001 / M:** восстановить согласованный byte minimum у auth validator,
|
||||
согласовать metadata и artifact; проверить один набор UTF-8 границ на
|
||||
frontend и configured backend boundary. Новый ADR нужен только при решении
|
||||
действительно изменить принятую password policy, а не при её восстановлении.
|
||||
|
||||
### Quick wins при работе над соответствующим owner
|
||||
|
||||
2. **DRY-002 / S:** один local projection mapper с сохранением allowlist,
|
||||
null/undefined и используемых enrichment publication fences. Это не требует
|
||||
сохранения discovery producer; его deletion scope — audit 04. Максимальный
|
||||
подтверждённый reuse benefit среди structural кандидатов этого аудита.
|
||||
3. **DRY-003 / S:** использовать один typed runtime tuple для schema и options;
|
||||
сохранить пустое form value и API null. Никакой tooling migration.
|
||||
4. **DRY-004 / S:** один predicate для четырёх codes; сохранить отдельный auth
|
||||
`UNAUTHORIZED`, Accounts subset и page recovery actions. Выполнять только
|
||||
с boundary proof сохранения temporary-error snapshot.
|
||||
|
||||
DRY-010 остаётся verification question о timer phase, не подтверждённым шагом
|
||||
migration и не защитой неиспользуемого return/retry. DRY-009 не требует
|
||||
сохранения discovery `request/refresh/drain`; их reachability/deletion — audit 04.
|
||||
KEEP findings не превращены в backlog на дополнительные layers/tests/rules.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **04 / Dead Code & Legacy:** observation compatibility в `frontend/src/shared/api/resource-query.ts:69–127`; проверить actual production consumers перед удалением, не удалять весь resource wrapper.
|
||||
- **04 / Dead Code & Legacy:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:66–70,84–86,149–190,227–267,340–495` и `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78,192–201`; default paths online, observe read-only, внешний refresh caller только в manual dispatch без найденного production request entry point; DRY-009 не защищает этот producer от удаления.
|
||||
- **04 / Dead Code & Legacy:** return/refresh/retry `frontend/src/features/tbank-connection/model/screen-sync.ts:37–61` потребляется только tests `screen-sync.test.tsx:37–50,68–78`; все восемь production calls отбросили return; deletion scope отдельно от живого timer `:22–35`.
|
||||
- **13 / Reliability & Security:** online catalog calls `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:149–165,295–317` и existing token translator `backend/src/integrations/tbank/tbank-token.service.ts:26–35`; проверить публичное invalid-token recovery, здесь отдельный error/security аудит не проводился.
|
||||
- **05 / Frontend Architecture:** ownership периодического чтения и app focus/reconnect `frontend/src/features/tbank-connection/model/screen-sync.ts:20–35`, `frontend/src/app/ui/App.tsx:13–20`; связан с DRY-010, не повод унифицировать разные staleTime.
|
||||
- **08 / Test Quality:** proof для DRY-001 должен различать byte/character limits; рассмотренный `backend/src/common/dto-validation.spec.ts:13–30` использует ASCII happy path, полноценный аудит auth tests не выполнялся.
|
||||
- **15 / SDD, Specs & Documentation:** `docs/adr/019-hourly-reads-and-online-instrument-reference.md:27–29`, `specs/openspec/specs/portfolio-view/spec.md:122–124`, `specs/AI_INDEX.md:15–31`; различать общую формулировку read interval и endpoint-specific freshness, не объявлять docs копии автоматически лишними.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, реально меняющий решение | Дешевейшая проверка | Что изменит ответ |
|
||||
| --- | --- | --- |
|
||||
| Есть ли принятое изменение password minimum с bytes на characters? | Проверить accepted auth decision/requirement у owner; existing spec и docs уже говорят о byte-policy и непринятом drift | Изменяет целевую policy DRY-001; без нового решения восстановить bytes |
|
||||
| Четыре codes по-прежнему имеют одинаковое membership во всех трёх contexts? | Existing MSW boundaries плюс unknown/temporary/auth code cases, без network | Если meanings расходятся, DRY-004 становится KEEP коротких local predicates |
|
||||
| Periodic reads должны синхронно повторять весь экран или каждый active resource после собственного успешного read? | Controlled-time MSW scenario mount/focus/reconnect/interval/unmount; сопоставить Accounts/Portfolio specs | Определяет, нужна ли screen clock и эквивалентен ли query-owned interval в DRY-010 |
|
||||
| Как native interval действует при disabled query и background tab в установленной версии? | Сверка документации версии и targeted existing owner test с disabled search, без install | Определяет безопасность и размер scheduling cleanup; пока UNVERIFIED |
|
||||
|
||||
### Финальная проверка полноты и ограничений
|
||||
|
||||
- Присутствуют все девять общих разделов, Knowledge Duplication Register,
|
||||
Intentional Duplication to Keep, minimal ownership model и сравнение вариантов
|
||||
local copies / owner / generated / shared.
|
||||
- Для каждого из десяти DRY-ID заполнены Decision, Severity, Confidence,
|
||||
Evidence, Claim, Current mechanism, Runtime consumer, Impact / actual cost,
|
||||
Why now, Minimum change, Guarantees at risk, Counterarguments / trade-offs,
|
||||
Verification or rollback, Effort и Related finding IDs. Для каждого
|
||||
SIMPLIFY дополнительно указан owner и допустимое направление imports.
|
||||
- Четыре actionable выводы отделены от пяти KEEP и одного INVESTIGATE.
|
||||
Неизмеренные savings и непроверенная эквивалентность явно обозначены.
|
||||
- Повторный consumer trace отделяет живой enrichment/shared fence DRY-009
|
||||
от discovery producer без найденного production entry point; восемь
|
||||
standalone `useScreenSync` calls не используют return. Связанные register,
|
||||
intentional KEEP, summary/top/minimum change и final table согласованы.
|
||||
- Boy Scout review отчёта: не добавлены competing policy values, generic
|
||||
shared owner, duplicate findings для одного механизма или обязательная
|
||||
tooling-зависимость от соседнего checkout. Исходники не правились.
|
||||
- После записи проверены собственный новый текст, whitespace и повторные
|
||||
HEAD/status всех пяти repos. Tracked/staged/unstaged source изменений нет.
|
||||
Отчёт ignored root allowlist-ом и существует только в разрешённом месте.
|
||||
- Статический audit не является runtime bug reproduction, успешным запуском
|
||||
tests, подтверждением DB atomicity, свежей генерацией OpenAPI или измерением
|
||||
производительности. Эти ограничения сохраняются после clean Git status.
|
||||
|
||||
## Machine-readable consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| DRY-001 | FIX | P2 | High | backend,frontend | backend/src/modules/auth/presentation/auth.dto.ts:43–74; frontend/src/features/auth/model/auth.schema.ts:36–43; frontend/src/shared/api/spec/openapi.json:5246–5250 | Разные единицы password minimum дают UI/API отказ | M | DRY-005,DRY-008 |
|
||||
| DRY-002 | SIMPLIFY | P3 | High | backend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–531; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–678 | Две одинаковые detail projections требуют двух правок whitelist | S | DRY-006,DRY-009 |
|
||||
| DRY-003 | SIMPLIFY | P3 | High | frontend | frontend/src/entities/bank-card/model/form.schema.ts:65–69; frontend/src/entities/bank-card/ui/BankCardForm/BankCardFields.vm.ts:19–22 | Schema и selectable options вручную повторяют один словарь | S | DRY-005,DRY-008 |
|
||||
| DRY-004 | SIMPLIFY | P3 | High | frontend | frontend/src/shared/api/client.ts:162–179; frontend/src/shared/api/resource-query.ts:37–67; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:59–64 | Три набора access-loss codes могут расходиться; actions оставить локальными | S | DRY-007,DRY-008 |
|
||||
| DRY-005 | KEEP | P2 | High | backend,frontend | frontend/src/entities/bank-card/model/money.ts:4–23; frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:53–131; backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38; backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–210 | Independent validation сохраняет precision и server trust boundary | S | DRY-001,DRY-003,DRY-008 |
|
||||
| DRY-006 | KEEP | P2 | High | backend,frontend | frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:23–98; frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–98; backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:114–178; backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:145–201 | Generic pipeline скроет разные dividend/coupon и quote semantics | S | DRY-002,DRY-007,DRY-010 |
|
||||
| DRY-007 | KEEP | P2 | High | frontend | frontend/src/shared/api/authenticated-query.ts:34–59; frontend/src/shared/api/resource-query.ts:37–149; frontend/packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx:5–17; frontend/packages/ui/src/StateView/StateView.tsx:5–27 | Wrappers несут session/resource/accessibility defaults | S | DRY-004,DRY-006,DRY-010 |
|
||||
| DRY-008 | KEEP | P3 | High | backend,frontend | backend/src/openapi.ts:4–10; frontend/src/entities/bank-card/api/types.ts:1–34; frontend/src/shared/api/generated/schema.d.ts:731–795; frontend/src/shared/api/problem-details.ts:1–28 | Generated copies сохраняют независимые repo inputs и open-code fallback | S | DRY-001,DRY-003,DRY-004,DRY-005 |
|
||||
| DRY-009 | KEEP | P2 | High | backend | backend/src/modules/investments/instruments/stocks/application/stock-market.service.ts:34–45; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–225,497–577; backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179 | Сохранить live enrichment guarantees/shared fence; discovery producer не входит в KEEP, deletion — audit 04 | S | DRY-002 |
|
||||
| DRY-010 | INVESTIGATE | P2 | Medium | frontend | frontend/src/features/tbank-connection/model/screen-sync.ts:22–35,53–61; frontend/src/app/ui/App.tsx:13–20; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84; frontend/src/entities/portfolio/api/usePortfolio.ts:3–5 | Проверить equivalence timer phase; восемь production callers не используют return/retry, runtime savings UNVERIFIED | M | DRY-006,DRY-007 |
|
||||
@@ -0,0 +1,746 @@
|
||||
# Аудит 04 — Dead Code & Legacy
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID / name:** 04 / Dead Code & Legacy.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Исполнитель:** один независимый исполнитель направления 04; другие
|
||||
субагенты не запускались.
|
||||
- **Результат:** исследование reachability и безопасных границ удаления.
|
||||
Изменён только этот отчёт вне application checkout.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
|
||||
| Checkout | Абсолютный путь | HEAD, проверенный самостоятельно | Начальный status |
|
||||
| --- | --- | --- | --- |
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | чистый |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | чистый |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | чистый |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | чистый |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | чистый |
|
||||
|
||||
**Метод фиксации:** в каждом checkout выполнены
|
||||
`git rev-parse HEAD && git status --porcelain=v1`; пустой porcelain означает
|
||||
отсутствие staged, unstaged и видимых Git untracked changes, а не отсутствие
|
||||
ignored файлов. Root allowlist исключает `audit-results/`
|
||||
(`.gitignore:1–7`), поэтому отчёт не должен появляться в обычном root status.
|
||||
|
||||
**Инструкции:** прочитаны AGENTS каждого checkout. Для backend и frontend
|
||||
прочитаны их собственные architecture, engineering и активированные
|
||||
conventions: HTTP/data/testing/integrations/security для backend;
|
||||
TypeScript/React/data/UI/testing/Storybook для frontend. Root conventions
|
||||
использованы только для root-границы отчёта. Specs выбраны через `AI_INDEX.md`
|
||||
и точные тематические targets; три актуальных требования нового потока,
|
||||
которых нет в индексе, прочитаны по известным абсолютным путям. Вложенных
|
||||
AGENTS в source-областях frontend/backend поиск не обнаружил.
|
||||
|
||||
**Skills:** установленный `ponytail-audit` загружен первым через Skill tool.
|
||||
Применены его `delete:` / `shrink:` теги, ранжирование по сокращению ненужной
|
||||
maintenance surface и запрет исправлений. Схема отчёта расширена обязательным
|
||||
протоколом 04. Числовой `net` не выдуман: deletion diff не создавался.
|
||||
Также загружены `graphify` и `verification-before-completion`.
|
||||
|
||||
**Graphify:** существующего `graphify-out/graph.json` не обнаружено в root
|
||||
и четырёх checkout: проверены списки корневых директорий, дополнительно Glob
|
||||
в frontend/backend. Graph query не выполнялся; graph generation, cache,
|
||||
manifest и другие artifacts не создавались. Отсутствие существующего graph
|
||||
— ограничение; выводы получены статической трассировкой актуальных source.
|
||||
|
||||
**Что выполнялось:** Read/Glob/Grep из соответствующих repo directories,
|
||||
read-only `rg -n -c` для явно указанных символов и `git` inspection.
|
||||
`technical-audit-2026-10-11.md` прочитан только как список гипотез;
|
||||
его выводы и оценка `~550 lines` не использованы как доказательство.
|
||||
Другие новые отчёты аудиторов не читались и не изменялись.
|
||||
|
||||
**Что не выполнялось:** Node-команды, install, build, codegen, migrations,
|
||||
full-suite, targeted tests, запуск приложения, запросы к реальным API,
|
||||
изменение config/source/lockfiles, commits, PR и генерация исследовательских
|
||||
инструментов. Это статический аудит, не runtime-воспроизведение.
|
||||
`npm run knip`, `deps`, frontend test scripts предварительно вызывают
|
||||
`build:packages` (`frontend/package.json:14–26`); их запуск нарушил бы
|
||||
заданную read-only границу. Их результаты из прежнего отчёта не наследуются.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт:** command-часть `TbankSyncService.request → dispatch` не имеет
|
||||
production entrypoint. Nest создаёт service для `status`, но controller
|
||||
предоставляет только GET. Удаление command-closure обосновано — DEAD-001.
|
||||
2. **Факт:** вслед за ней обнаружен менее очевидный dead producer:
|
||||
`CatalogDiscoveryService.request/refresh/drain`. Реальные HTTP search,
|
||||
detail и ticker читают онлайн либо вызывают persisted reader только с
|
||||
`observe=true`; старый request-driven импорт каталога запускают прямые
|
||||
тесты и недоступная manual orchestration — DEAD-002.
|
||||
3. **Факт:** frontend `observe()` используется только двумя прямыми
|
||||
тестовыми вызовами; все восемь production consumers `useScreenSync`
|
||||
игнорируют его return value, а `_scope` вообще не читается.
|
||||
Можно удалить эти API-хвосты, сохранив действующий timer — DEAD-004/005.
|
||||
4. **Факт и граница:** статусный GET и backend `sync=observe` действительно
|
||||
публично достижимы. Отсутствие их использования в текущей SPA не доказывает
|
||||
отсутствия внешних consumers или старых DB rows. Их удаление сейчас
|
||||
**не подтверждено** — DEAD-006/007, `INVESTIGATE`.
|
||||
5. **Риск ошибочной зачистки:** planner операций, hourly lifecycle, candle
|
||||
loader, shared source repository и connection/attempt fences остаются
|
||||
живыми. Массовое удаление по словам `screen`, `catalog` или `sync` может
|
||||
остановить данные либо каскадно удалить свечи — DEAD-008–011, `KEEP`.
|
||||
6. **Итог:** 5 actionable findings: **P0 = 0, P1 = 0, P2 = 2, P3 = 3**.
|
||||
Отдельно **KEEP = 4**, **INVESTIGATE = 2**. Для непроверенной внешней
|
||||
совместимости указан `UNVERIFIED`; экономия времени/строк не измерена.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Фактическая карта entrypoints
|
||||
|
||||
| Вход / поток | Проверенная цепочка и owner | Следствие для reachability |
|
||||
| --- | --- | --- |
|
||||
| SPA bootstrap | `frontend/src/main.tsx:1–23` → `src/app/ui/App.tsx:23–43` → `src/app/routes/AppRoutes/AppRoutes.tsx:9–103,105–257` | Lazy imports являются настоящими consumers page exports. CSS, i18n и dayjs locale — side effects, не orphan imports. |
|
||||
| SPA routes | `AppRoutes.tsx:105–257`: login/register; cards/card; stocks/stock; bonds/bond; accounts/account/events/income/operations; settings; wildcard | Страницы доступны через router, включая динамические chunks; отсутствие статического прямого import не делает их мёртвыми. |
|
||||
| Backend HTTP | `backend/src/main.ts:44–60` → `src/app.module.ts:18–36` → controllers | Prefix, validation, cookies, error boundary и shutdown hooks задаются `src/main.ts:19–42`; controller decorators являются registration. |
|
||||
| Controller families | `backend/src/modules/auth/presentation/auth.controller.ts:51`; `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:79`; `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.controller.ts:15`; `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:15`; `backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:18`; `backend/src/modules/investments/income/income-analytics.controller.ts:20`; `backend/src/modules/investments/upcoming-events/upcoming-events.controller.ts:18`; `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:48`; `backend/src/modules/investments/instruments/bonds/presentation/bond.controller.ts:37`; `backend/src/modules/investments/tbank-connection/tbank-connection.controller.ts:33`; `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31` | Проверены registrations всех этих семейств поиском `@Controller`, root module и нужными application chains; это не полный семантический аудит CRUD/auth. |
|
||||
| Hourly job | `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:24–25` → scheduler `onModuleInit:48–50` → `tick:56–71` → `reserveHourly` / `walk` | Один native timer, durable work и source owners; запуск не зависит от frontend. |
|
||||
| Source queues | Portfolio `queueSnapshot`, operations sync, metadata, enrichment, candles используют `setImmediate`; declarations найдены в source, живые примеры traced ниже | Удаление manual dispatcher не даёт права удалять все очереди с похожими именами. |
|
||||
| Production CLI | `backend/package.json:15–18`; `compose.yaml:24–38` | Backend запускает `dist/src/main.js`; отдельная migration service — Prisma CLI. В manifest не найден CLI, вызывающий manual sync или persisted catalog producer. |
|
||||
| Test dynamic entrypoint | `backend/test/fixtures/tbank-sync-crash.cjs:1–17` | Динамические require ведут в operations sync/repository, не в `TbankSyncService` или `CatalogDiscoveryService`. Fixture не запускался. |
|
||||
| OpenAPI tooling | `backend/scripts/generate-openapi.cjs:19–31` → AppModule preview → `src/openapi.ts:6–10` | Generator читает зарегистрированный controller contract; не вызывает service methods по именам. Наличие DTO decorator само по себе не публикует DTO. |
|
||||
| Storybook / packages | `frontend/.storybook/main.ts:3–7`; `packages/ui/package.json:9–22`; `src/app/ui/App.tsx:3–8` | Stories — дополнительные entrypoints; UI CSS явно side-effectful. Feature/query candidates не экспортируются public UI package. |
|
||||
| Env / imports | `backend/src/config/env.validation.ts:9–33,102–113`, `.env.example:1–10`, `src/app.module.ts:18–36`; `frontend/vite.config.ts:54–61`, `tsconfig.app.json:8–20` | Env задаёт транспорт/config, но не включает manual sync module/method или старую catalog producer ветку. Alias `@/` ведёт в source. |
|
||||
|
||||
### 3.2. Метод отбора и счёта
|
||||
|
||||
Исследованы **12 кандидатов C01–C12**, отобранных по стоимости сопровождения
|
||||
и риску ошибочного удаления: старая orchestration, связанные producer paths,
|
||||
orphan DTO, frontend API-хвосты, публичная compatibility и защищённые live
|
||||
owners. Число 12 — число строк Reachability Ledger, не результат analyzer.
|
||||
Findings — 11: C01/C02 образуют одну deletion closure DEAD-001.
|
||||
|
||||
Порядок: declaration → exports/DI/registration → все найденные callers →
|
||||
реальный runtime entrypoint → external/public accessibility → контрпример.
|
||||
Поиск применялся к source, затем отдельно tests/scripts/artifacts, чтобы
|
||||
не принять direct test invocation за пользовательский путь.
|
||||
|
||||
**Воспроизводимые search counts:** это количество matching lines, а не число
|
||||
AST references; вывод вручную разделён по declarations/imports/calls.
|
||||
Поиск не считается самостоятельным доказательством отсутствия callers.
|
||||
|
||||
| Поиск и границы | Результат и независимая проверка |
|
||||
| --- | --- |
|
||||
| Q01, backend source/tests/scripts, команда ниже | `TbankSyncService` найден только в своём service, controller и module; `sync.request(` — 0 строк. Controller calls только `status`. Для `normalizeScreen(`: 1 declaration, 1 вызов в dead request, 9 строк вызовов в `tbank-screen.spec.ts`. DTO Accepted — 1 declaration, 0 consumers. |
|
||||
| Backend: owner-aware поиск `this.discovery.*` в instruments и чтение обоих catalog services | 6 live delegation call sites: search/detail/byTicker у stocks и bonds. Ни один не вызывает `read/request/refresh` напрямую. `discovery.refresh` найден в manual dispatcher. В discovery owner три вызова `this.read` находятся после online-return ветки и получают `observe=true`; `this.request` — в `read(!observe)` и `refresh`. |
|
||||
| Q02, frontend source/packages/Storybook/scripts, команда ниже | `observe()` — 2 строки, обе в `resource-query.test.tsx:63,167`; production вызовов 0. `useScreenSync(` — 8 production call lines, 2 test call lines, 1 declaration. Все 8 VM прочитаны целиком: return hook не захватывается. |
|
||||
| Q03, frontend source/packages/Storybook, команда ниже | `tbank-sync` — 2 строки JSON snapshot и 1 generated route declaration; runtime call site 0. Литерал `sync: "observe"` — 1 строка в `observationKey`. Accepted DTO — 0. |
|
||||
| Q04, backend OpenAPI artifact, команда ниже | 2 matching lines: GET path и operationId; Accepted DTO отсутствует. Artifact не регенерировался, поэтому свежесть runtime parity — `UNVERIFIED`; controller/source metadata дополнительно проверены. |
|
||||
|
||||
Команды выполнены с `workdir` соответствующего checkout, без `cd` и записи
|
||||
output-файлов. Exact regex приведены вне таблицы, чтобы Markdown не подменял
|
||||
alternation и не создавал лишние колонки.
|
||||
|
||||
```sh
|
||||
# Q01 — backend
|
||||
rg -n -c 'TbankSyncService|sync\.request\(|normalizeScreen\(|TbankSyncAcceptedDto|discovery\.(read|request|refresh)\(' src test scripts --glob '*.{ts,cjs,mjs}'
|
||||
# Q02 — frontend
|
||||
rg -n -c '\.observe\(|\["observe"\]|\[.observe.\]|\buseScreenSync\(' src packages .storybook scripts --glob '*.{ts,tsx,cjs,mjs}' --glob '!**/generated/**' --glob '!**/dist/**'
|
||||
# Q03 — frontend
|
||||
rg -n -c 'tbank-sync|TbankSyncAcceptedDto|sync: "observe"' src packages .storybook --glob '!**/dist/**'
|
||||
# Q04 — backend
|
||||
rg -n -c 'TbankSyncAcceptedDto|TbankSyncController_status|/api/v1/tbank-sync' openapi.json
|
||||
```
|
||||
|
||||
**Dynamic checks:** source search по `ModuleRef`, reflection/getMetadata,
|
||||
computed `request/observe`, `import()/require`, factory/alias DI и cron/timers
|
||||
не выявил alternate entrypoint кандидатов на DELETE. Проверены явные
|
||||
`@Injectable`, `@Module`, `@Controller`, `@Get`, `ApiExtraModels`, `useExisting`
|
||||
и module exports. В backend `useFactory` используется для logging/auth,
|
||||
`TBANK_ACCESS` alias — для instruments (`instruments.module.ts:24`);
|
||||
кандидатов он не dispatch-ит. В frontend dynamic imports представлены явными
|
||||
router chunks; Storybook собирает stories по glob. No-match ограничен
|
||||
этими checkout: неизвестный deployed consumer не исключён.
|
||||
|
||||
### 3.3. Coverage и ограничения выборки
|
||||
|
||||
- Сфокусированы investments synchronization/catalog и frontend consumers,
|
||||
где после перехода архитектуры есть сильные гипотезы с большими closures.
|
||||
- Банковские/auth routes проверены на существование root registration;
|
||||
correctness их бизнес-логики здесь не исследовалась.
|
||||
- Tests прочитаны как callers и deletion dependencies. Тестовое покрытие
|
||||
проекта, mutation score и качество всех assertions не оценивались.
|
||||
- Specs/docs рассматриваются как intended behavior и compatibility context,
|
||||
а не доказательство текущего исполнения. Архивы не являются candidates.
|
||||
- Generated/dist, coverage, реальные `.env`, DB, deployment logs и секреты
|
||||
не служили source call graph; lockfiles/dependency removal вне scope 04.
|
||||
- Runtime trace, bundle size, CI minutes, tokens и размер потенциального diff
|
||||
не измерены. Статическая карта не доказывает отсутствие внешних клиентов.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
Severity actionable findings отражает текущую engineering cost, а не обещанную
|
||||
экономию. В KEEP/INVESTIGATE Severity обозначает риск ошибочного удаления;
|
||||
эти строки не являются подтверждёнными дефектами и не включены в actionable
|
||||
severity totals. Ни один finding не предлагает исправление в этом аудите.
|
||||
|
||||
### DEAD-001 — Удалить недоступную manual screen orchestration вместе с closure
|
||||
|
||||
- **Decision:** DELETE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:41–51,54–311`; `tbank-sync.controller.ts:31–41`; `tbank-sync.module.ts:23–25`; `tbank-sync.repository.ts:62–109,135–169`; `tbank-screen.ts:8–122`; `tbank-screen.spec.ts:1–75`. Последние пять путей относительны той же `backend/src/modules/investments/tbank-sync/`.
|
||||
- **Claim:** у `request → normalizeScreen → reserve → setImmediate(dispatch)`
|
||||
нет production entrypoint; соответственно manual child planner и writer
|
||||
progress поддерживаются без пользовательского сценария.
|
||||
- **Current mechanism / runtime consumer:** service зарегистрирован Nest,
|
||||
но его единственный production consumer — constructor controller,
|
||||
вызывающий `status`. `request` не имеет route/scheduler decorator,
|
||||
service не экспортирован TbankSyncModule, reflection dispatcher не найден.
|
||||
`normalizeScreen` фактически вызывают direct unit tests и недоступный request.
|
||||
- **Impact / actual cost:** лишняя orchestration source owners, ручные
|
||||
reservations, field normalization и отдельный тестовый сценарий;
|
||||
**Impact unmeasured**. Runtime RPC savings не заявляются: путь уже недоступен.
|
||||
- **Why now:** текущие source entrypoints и
|
||||
`specs/openspec/specs/tbank-data-sync/spec.md:9–25` передали обновление hourly
|
||||
owner и запрещают manual commands. Это не предположение только по ADR.
|
||||
- **Minimum change:** удалить request/dispatch, `reservations`, exclusive
|
||||
DI/imports/constants, repository `reserve/progress`, normalization и её
|
||||
tests. Сохранить весь status reader и его compatibility types, пока DEAD-006
|
||||
не решён; сохранить shared source owners и operations planner.
|
||||
- **Guarantees at risk:** access/revision checks внутри живого status,
|
||||
source CAS, hourly lifecycle, shared metadata/portfolio/candle owners.
|
||||
Не удалять эти owners вместе с исключёнными constructor injections.
|
||||
- **Counterarguments / trade-offs:** создание service Nest не равно вызову
|
||||
всех его методов. Возвращение manual sync было бы новым продуктовым решением;
|
||||
restore из Git возможно. Старые screen work могут ещё читаться status:
|
||||
поэтому reader не входит в DELETE.
|
||||
- **Verification or rollback:** после отдельного cleanup — type/DI proof,
|
||||
отсутствие POST command в configured OpenAPI, HTTP status compatibility,
|
||||
существующие local-read/hourly scenarios. Rollback — вернуть code-only
|
||||
closure одним revert; DB/schema не менять.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** DEAD-002, DEAD-006, DEAD-008, DEAD-009, DEAD-011.
|
||||
|
||||
### DEAD-002 — Удалить недоступный persisted catalog discovery producer
|
||||
|
||||
- **Decision:** DELETE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:39–64,66–86,143–149,220–227,340–496`; `instruments/stocks/application/stock-catalog.service.ts:21–31,59–83`; `instruments/bonds/application/bond-catalog.service.ts:24–34,58–82`; `instruments/infrastructure/catalog-discovery.repository.ts:341–464`; `backend/test/tbank-catalog-discovery.e2e-spec.ts:85–118,179–196,231–244,270–299`. Три укороченных `instruments/` пути относительны `backend/src/modules/investments/`.
|
||||
- **Claim:** producer `request/refresh/drain` production-недостижим в текущем
|
||||
graph. После DEAD-001 исчезает даже его единственный caller из недоступной
|
||||
ветки. HTTP `sync=observe` сохраняет reader, но не включает этот writer.
|
||||
- **Current mechanism / runtime consumer:** ordinary search вызывает
|
||||
`onlineSearch`; ordinary detail/byTicker выходят через online ветки.
|
||||
Только observe-ветки доходят до `read`, всегда с `true`, и потому пропускают
|
||||
`if (!observe) request`. Единственный caller `refresh` в production source
|
||||
— недоступный manual dispatcher.
|
||||
Прямые тесты `service.read(..., false/default)` и `service.request(...)`
|
||||
реально запускают queue, но не являются HTTP consumers.
|
||||
- **Impact / actual cost:** producer map/queue, forced joins, completions,
|
||||
logger dependency и отдельная persisted discovery publication поддерживают
|
||||
прежнее поколение поведения. **Impact unmeasured**; это сокращение maintenance,
|
||||
не устранение измеренного fan-out живого online search.
|
||||
- **Why now:** онлайн-входы уже существуют; persisted discovery writer не
|
||||
обслуживает ни их, ни hourly events. Online-only requirement независимо
|
||||
прочитан: `specs/openspec/specs/online-instrument-information/spec.md:8–17`.
|
||||
- **Minimum change:** после DEAD-001 удалить Producer/state/request/refresh/drain,
|
||||
dead `read(!observe)` branch и только exclusive repository `publish`.
|
||||
Сохранить reader `read/work`, `enrichment`, normalization/selection и online
|
||||
error projection. Не удалять repository `reserve`, `finish`, `readSources`,
|
||||
`publishSource`: их использует live CatalogEnrichmentService.
|
||||
- **Guarantees at risk:** shared source writes, CAS/invalid-token isolation,
|
||||
legacy observe read, UID/FIGI identity и candle FK. Schema и данные не входят
|
||||
в эту code-only deletion.
|
||||
- **Counterarguments / trade-offs:** public `instruments.api.ts:2` экспортирует
|
||||
provider внутри приложения, а не published npm method contract;
|
||||
`backend/package.json:6,11–38` — private application без CLI producer.
|
||||
HTTP compatibility может оправдать persisted reader (DEAD-007), но не writer,
|
||||
которого эти routes не вызывают. Прямые tests доказывают механизм,
|
||||
не пользовательскую reachability.
|
||||
- **Verification or rollback:** переназначить/удалить direct producer-only
|
||||
cases в `test/tbank-catalog-discovery.e2e-spec.ts`; сохранить проверку живого
|
||||
online selection/whitelist и legacy reader через существующие HTTP suites.
|
||||
Проверить unchanged `publishSource`/candles на isolated SQLite в cleanup PR.
|
||||
Rollback — code-only revert без DB migration.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-007, DEAD-010, DEAD-011.
|
||||
|
||||
### DEAD-003 — Удалить orphan `TbankSyncAcceptedDto`
|
||||
|
||||
- **Decision:** DELETE.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.dto.ts:212–218`; `tbank-sync.controller.ts:13–27,34–40`; `backend/test/openapi-contract.e2e-spec.ts:27–55`; `frontend/src/shared/api/spec/openapi.json:5102–5123`. Controller путь относителен той же backend sync directory.
|
||||
- **Claim:** DTO command acceptance объявлен, но не используется controller,
|
||||
service, ApiExtraModels, scripts или публикуемым snapshot.
|
||||
- **Current mechanism / runtime consumer:** module DTO загружается живым GET,
|
||||
и decorators этой orphan class регистрируют её собственные metadata.
|
||||
Но нет consumer, который включал бы её в response schema или исполнял её
|
||||
validation. Status class — отдельный live consumer `investmentScreenSchema`.
|
||||
- **Impact / actual cost:** небольшой устаревший export рядом с действующими
|
||||
response DTO; **Impact unmeasured**. Его наличие может создавать ложное
|
||||
впечатление сохранённой POST capability.
|
||||
- **Why now:** POST отсутствует в actual controller и snapshot; existing
|
||||
contract test явно ожидает отсутствие `/api/v1/tbank-sync` command route.
|
||||
- **Minimum change:** удалить только class `TbankSyncAcceptedDto`.
|
||||
`InvestmentScreenDto`, screen schema, derived DTO и `TbankSyncStatusDto`
|
||||
остаются нужны public status response.
|
||||
- **Guarantees at risk:** accidental изменение GET schema при избыточном
|
||||
удалении shared schema; сама Accepted class не является опубликованным DTO.
|
||||
- **Counterarguments / trade-offs:** `export class` и decorators не равны
|
||||
published contract; generator следует зарегистрированным routes.
|
||||
Artifact parity со свежей сборкой здесь не запускалась — `UNVERIFIED`.
|
||||
- **Verification or rollback:** targeted OpenAPI schema comparison при cleanup;
|
||||
Accepted absent, Status и его discriminator unchanged. Rollback — вернуть class.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-006.
|
||||
|
||||
### DEAD-004 — Удалить only-test frontend `observe` API
|
||||
|
||||
- **Decision:** DELETE.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/shared/api/resource-query.ts:69–82,104–127,144`; `src/shared/api/resource-query.test.tsx:40–71,139–173`; `src/shared/api/index.ts:12–15`; `frontend/package.json:2–5`; `src/features/tbank-connection/model/screen-sync.ts:28–50`. Укороченные `src/` пути относительны frontend.
|
||||
- **Claim:** `observationKey` и возвращаемый `observe` не используются
|
||||
production callers; они поддерживают отдельный transport режим только для
|
||||
direct tests. Удаление frontend callback не означает удаления backend query.
|
||||
- **Current mechanism / runtime consumer:** live `useResourceQuery` вызывается
|
||||
entity hooks, но `observe()` — только tests `:63,167`. Callback вручную
|
||||
переписывает queryKey для transport, cancel/query/select policy;
|
||||
production screen timer вызывает обычный `refetch`.
|
||||
- **Impact / actual cost:** дополнительный путь cancel/cache replacement и
|
||||
его own tests без UI consumer; **Impact unmeasured**. Bundle/runtime эффект
|
||||
не измерен, tree-shaking не проверялся.
|
||||
- **Why now:** текущий runtime использует resource GET и `refetch`; источник
|
||||
attempt state — metadata, а не orchestration notification.
|
||||
- **Minimum change:** убрать `observationKey`, `observe`, его return member
|
||||
и exclusive imports (`QueryFunctionContext`, локальный import session snapshot).
|
||||
Удалить первый observe-only test; race-сценарий `:139–173` перенести на живой
|
||||
cancellation/refetch boundary, если он нужен для общей гарантии.
|
||||
- **Guarantees at risk:** не менять `useAuthenticatedQuery`,
|
||||
`invalidateResourceAccess`, projected data/meta, canonical query identity,
|
||||
session switching и обычный `refetch` envelope.
|
||||
- **Counterarguments / trade-offs:** `.ai/conventions/data.md:61–63` всё ещё
|
||||
описывает наблюдение, но правило не создаёт отсутствующий production caller.
|
||||
SPA private, feature не public package; dynamic/router/story consumers
|
||||
проверены. Backend `sync=observe` остаётся доступен внешнему HTTP клиенту;
|
||||
наличие реального такого клиента здесь не подтверждено.
|
||||
- **Verification or rollback:** существующие resource tests `:72–138,174–210`
|
||||
и auth/connection boundary tests при cleanup; synthetic MSW, без реальной сети.
|
||||
Rollback — вернуть callback/tests, никакой data migration.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DEAD-005, DEAD-007, DEAD-011.
|
||||
|
||||
### DEAD-005 — Сократить unused facade `useScreenSync`, сохранив live timer
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:1–18,22–61`; `screen-sync.test.tsx:21–80` в той же directory; `src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:21–24`; `src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84`; `src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts:96–99`; `src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:67–75`; `src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:66–69`; `src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:12–13`; stock/bond VM `:45–55`. Все `src/` пути относительны frontend; полные stock/bond пути перечислены в Ledger C06.
|
||||
- **Claim:** `_scope`, returned manual refresh/retry/status facade и требуемые
|
||||
только этой facade resource fields не имеют production consumers.
|
||||
Timer `:28–35` имеет восемь route-reachable callers и не является dead code.
|
||||
- **Current mechanism / runtime consumer:** все 8 VM вызывают hook как отдельное
|
||||
expression statement. Возвращённые `refresh/refreshing/retry/sourceError/
|
||||
transportError` не попадают в VM/view. Scope не читается даже самим hook.
|
||||
Единственные вызовы returned commands — прямые tests `:50,78`.
|
||||
- **Impact / actual cost:** caller собирает screen descriptor, hook создаёт
|
||||
неиспользуемые commands/status, тип навязывает `queryKey/meta/isError/isFetching`.
|
||||
**Impact unmeasured**; allocations/время render не измерены.
|
||||
- **Why now:** hook уже стал периодическим GET reader, но сохранил facade
|
||||
прежней screen orchestration.
|
||||
- **Minimum change:** убрать scope аргумент и все восемь descriptor objects;
|
||||
убрать only-test returned commands/status и exclusive fields/imports.
|
||||
Оставить `resourcesRef`, актуализацию resources, native interval и cleanup.
|
||||
Перепривязку timer к Query options этот finding не требует.
|
||||
- **Guarantees at risk:** нельзя удалить сам hook вызов: он обеспечивает
|
||||
reread открытого экрана. Нельзя убрать cleanup или обновление resourcesRef:
|
||||
смена route/filter должна читать актуальные queries.
|
||||
- **Counterarguments / trade-offs:** тесты refresh/retry действительно исполняют
|
||||
callbacks, но не делают их доступными пользователю. UX потребность в retry
|
||||
может потребовать нового подключения к view; сейчас такого consumer нет.
|
||||
Таймерная политика живого пути — отдельный audit 02/07 вопрос.
|
||||
- **Verification or rollback:** удалить direct facade-only cases,
|
||||
проверить существующий hook с fake time: reread актуального resource и
|
||||
прекращение после unmount. Page behavior и GET-only boundary сохранить.
|
||||
Rollback — вернуть facade/caller descriptors без изменений данных.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DEAD-004, DEAD-009.
|
||||
|
||||
### DEAD-006 — Статусный GET и старые `screen:` rows требуют compatibility proof
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:21–41`; `tbank-sync.service.ts:313–391`; `tbank-sync.repository.ts:111–133`; `frontend/src/shared/api/spec/openapi.json:5102–5185`; `backend/test/openapi-contract.e2e-spec.ts:30–55`; `docs/architecture/http-api.md:46–52`. Укороченные sync пути относительны backend sync directory.
|
||||
- **Claim:** GET `/api/v1/tbank-sync/{id}` локально не вызывается SPA, но
|
||||
зарегистрирован, авторизован и опубликован. Нельзя назвать его confirmed dead
|
||||
или удалить screen reader вместе с недоступным writer. Наличие реальных
|
||||
внешних consumers — UNVERIFIED; High относится к reachability.
|
||||
- **Current mechanism / runtime consumer:** любой авторизованный HTTP consumer
|
||||
может вызвать controller → `status`. Reader обслуживает `screen:` intent и
|
||||
source works; access/revision проверяется до и после observation.
|
||||
Старые screen work могли сохраниться от предыдущей версии приложения.
|
||||
- **Impact / actual cost:** retained compatibility несёт reader/types/schema
|
||||
complexity; **Impact unmeasured**. Ошибочное удаление — breaking response/route.
|
||||
- **Why now:** writer можно удалить отдельно, оставив минимальную проверенную
|
||||
границу совместимости. Deadline поддержки в прочитанных источниках не найден.
|
||||
- **Minimum change:** сейчас не удалять endpoint/reader/derived DTO. Получить
|
||||
список поддерживаемых consumers и решить срок поддержки; затем отдельно
|
||||
согласовать withdrawal route либо поддержку только source status.
|
||||
- **Guarantees at risk:** user/connection ownership, old work observation,
|
||||
stable scope/date discriminator и публичная wire compatibility.
|
||||
- **Counterarguments / trade-offs:** docs говорит, что command/status не текущая
|
||||
AS-IS модель; это подтверждает legacy intent, но не отсутствие live HTTP route
|
||||
и external clients. Отсутствие frontend calls — слабое отрицательное evidence.
|
||||
- **Verification or rollback:** самый дешёвый proof — ответ owner API о
|
||||
consumers плюс существующая обезличенная access-log выборка и aggregate count
|
||||
`screen:` work из disposable DB copy. Ничего из этого здесь не получено.
|
||||
Rollback будущего изменения — возврат endpoint/schema до destructive migration.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-003, DEAD-007.
|
||||
|
||||
### DEAD-007 — Backend observe/persisted reference compatibility внешне достижима
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:58–82,112–144`; `instruments/bonds/presentation/bond.controller.ts:55–89,102–114`; `instruments/application/catalog-discovery.service.ts:84–141,168–217,251–293`; `backend/src/modules/investments/investment-resource.dto.ts:15–24`; `docs/architecture/http-api.md:50–52`; `docs/adr/019-hourly-reads-and-online-instrument-reference.md:22–26`. Укороченные instruments пути относительны `backend/src/modules/investments/`.
|
||||
- **Claim:** persisted catalog/detail/ticker read branches являются legacy
|
||||
public contract, а не confirmed dead. Из DELETE old producer не следует
|
||||
DELETE observe readers или shared instrument storage. Нужность долгой
|
||||
поддержки — UNVERIFIED; High относится к подтверждённому механизму.
|
||||
- **Current mechanism / runtime consumer:** query DTO допускает `sync=observe`;
|
||||
controllers переводят его в boolean, передают catalog services, которые
|
||||
достигают reader branches. Это реальный внешний вход даже без SPA callback.
|
||||
- **Impact / actual cost:** две модели справочного чтения рядом — online и
|
||||
persisted compatibility; **Impact unmeasured**. API withdrawal требует
|
||||
consumer transition, а blanket data purge рискует candle coverage.
|
||||
- **Why now:** online-only intended behavior уже принят, но compatibility
|
||||
сохранена source/HTTP metadata. Нужно ограничить именно поддержку старого
|
||||
reader, а не удалять shared business/persistence primitives по названию.
|
||||
- **Minimum change:** сохранить текущий wire parameter/reader; отдельно решить
|
||||
endpoint-specific retirement. При подтверждённой ненужности можно перестать
|
||||
направлять reference reads в old branches, затем удалить их closure.
|
||||
Не удалять общую `InvestmentReadQueryDto` для всех ресурсов автоматически.
|
||||
- **Guarantees at risk:** passive observation, distinction unknown/empty/
|
||||
ambiguous lookup, account access, precision, persisted calendars и candles.
|
||||
- **Counterarguments / trade-offs:** requirement запрещает долговременную
|
||||
самостоятельную reference copy, но не доказывает безопасность удаления
|
||||
`TbankInstrument` identity: у неё cascade FK на candles/source rows
|
||||
(`backend/prisma/schema.prisma:148–229`). Внешние consumers не проверены.
|
||||
- **Verification or rollback:** consumer list + metadata/access logs без
|
||||
реальных API calls; agreed contract transition и isolated DB/FK proof перед
|
||||
любым data removal. Rollback code routing возможен, восстановление данных
|
||||
потребует backup; поэтому data purge сейчас не предложен.
|
||||
- **Effort:** M.
|
||||
- **Related finding IDs:** DEAD-002, DEAD-004, DEAD-006, DEAD-010, DEAD-011.
|
||||
|
||||
### DEAD-008 — Planner и interval helpers живы через Account Ledger
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/investment-sync-plan.ts:67–140,271–328`; `operations/application/brokerage-operations.service.ts:16–19,69–85,120–150,203–209`; `operations/application/brokerage-operations-sync.service.ts:9,106–112`; `investment-sync-plan.spec.ts:100–134`. Укороченные пути относительны `backend/src/modules/investments/`.
|
||||
- **Claim:** нельзя удалить `planInvestmentScreen`, `accountHistory`,
|
||||
`ledgerRange` или их файл вслед за manual sync: они обслуживают живой Ledger.
|
||||
- **Current mechanism / runtime consumer:** operations controller → read →
|
||||
planner для `screen='operations'`, membership gating и selected interval →
|
||||
`sync.readPlan`. History/read metadata также вызывает accountHistory;
|
||||
interval helpers используются независимо от dead request.
|
||||
- **Impact / actual cost:** broad screen vocabulary содержит прежние ветки,
|
||||
но ядро живо; **Impact unmeasured**. Whole-file deletion ломает получение
|
||||
первого отсутствующего интервала, границы времени и history state.
|
||||
- **Why now:** это конкретный counterexample прежней гипотезе «screen planner
|
||||
принадлежит только manual orchestration».
|
||||
- **Minimum change:** сохранить live planner/helpers. После DEAD-001 допустим
|
||||
отдельный узкий reachability pass по вариантам screen, если это стоит усилий;
|
||||
автоматическое удаление всех planner tests недопустимо.
|
||||
- **Guarantees at risk:** account scope, membershipKnown gating, inclusive/
|
||||
exclusive end, historical boundaries, нерасширение произвольного интервала.
|
||||
- **Counterarguments / trade-offs:** немало branches после removal manual writer
|
||||
станет only-test reachable. Это повод сузить конкретные варианты с equivalence
|
||||
proof, а не удалить финансовые interval rules. В 04 это не generic refactor.
|
||||
- **Verification or rollback:** current ledger HTTP/local-read/interval tests
|
||||
при любом будущем narrowing; сравнить selected intervals, unknown membership,
|
||||
all/account и date-only/instant boundaries. Сейчас deletion отсутствует.
|
||||
- **Effort:** S.
|
||||
Здесь и в других KEEP effort относится к сохранению deletion boundary;
|
||||
narrowing/migration оцениваются отдельно.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-009.
|
||||
|
||||
### DEAD-009 — Hourly scheduler является runtime owner, а не legacy provider
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/app.module.ts:14,35`; `src/modules/investments/tbank-sync/tbank-sync.module.ts:24–25`; `tbank-hourly.scheduler.ts:28–87,119–175`; `tbank-sync.repository.ts:174–243,245–286`; `compose.yaml:32–35`; `specs/openspec/specs/hourly-investment-sync/spec.md:8–40`. Укороченные sync пути относительны backend sync directory.
|
||||
- **Claim:** отсутствие HTTP callers `tick` или прямых тестов не делает
|
||||
scheduler dead; Nest lifecycle запускает его native timer.
|
||||
- **Current mechanism / runtime consumer:** `onModuleInit → setInterval(tick)`
|
||||
читает connections, резервирует work и через setImmediate запускает owners
|
||||
membership/portfolio/operations/events; `onModuleDestroy` очищает timer.
|
||||
Метаданные ресурсов читают repository hourly result.
|
||||
- **Impact / actual cost:** runtime coordination необходима для обновления
|
||||
данных при закрытой SPA; **Impact unmeasured**. Удаление отключило бы
|
||||
normal personal refresh. Корректность lifecycle отдельно не сертифицируется.
|
||||
- **Why now:** whole-folder cleanup `tbank-sync/` выглядел бы коротким,
|
||||
но смешивает dead manual writer с current root entrypoint.
|
||||
- **Minimum change:** сохранить scheduler, registration, repository hourly
|
||||
методы и DTO; убирать только manual command closure DEAD-001.
|
||||
- **Guarantees at risk:** запуск без экрана, single-attempt fence, generation,
|
||||
остановка timer, section result visibility и отсутствие catch-up очереди.
|
||||
- **Counterarguments / trade-offs:** наличие lifecycle hook не доказывает
|
||||
правильность всех failure branches. Подозрения на их correctness направлены
|
||||
владельцу audit 13/08, а не использованы для рекомендации DELETE.
|
||||
- **Verification or rollback:** перед cleanup DI/module proof и существующий
|
||||
source-owner behavior; scheduler error/time scenarios — отдельные проверки
|
||||
при изменении его поведения. Сейчас механизм сохраняется.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-005, DEAD-008, DEAD-011.
|
||||
|
||||
### DEAD-010 — Candle loader и staging не удаляются вместе с manual force caller
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P2.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/candle.service.ts:101–124,206–261,264–326`; `instruments/stocks/application/stock-market.service.ts:54–64`; `instruments/bonds/application/bond-market.service.ts:58–68`; `instruments/infrastructure/candle.repository.ts:137–228,257–284`; `backend/test/tbank-candles-persistence.e2e-spec.ts:117–200,486–501`; `specs/openspec/specs/tbank-data-sync/spec.md:16–22`. Укороченные instruments пути относительны `backend/src/modules/investments/`.
|
||||
- **Claim:** `CandleService.request`, queue/stage/publication и coverage
|
||||
runtime-required: read запускает первое получение отсутствующего диапазона.
|
||||
- **Current mechanism / runtime consumer:** GET stock/bond candles → market
|
||||
service → `history.read` → при отсутствии coverage `request → drain/load`.
|
||||
Отдельно enrichment.ensureDetail использует shared sources. Manual force
|
||||
— не единственный caller source owner.
|
||||
- **Impact / actual cost:** механизм обеспечивает запрошенный диапазон,
|
||||
точные decimal strings, empty coverage и transactional publication;
|
||||
**Impact unmeasured**. Удаление owner сделает cold candles недоступными.
|
||||
- **Why now:** соседство с manual request повышает риск удалить весь loader,
|
||||
его source repository или associated tests без проверки hidden read caller.
|
||||
- **Minimum change:** сохранять ordinary request/staging/coverage. Нужность
|
||||
отдельно forced/priority вариантов после DEAD-001 **UNVERIFIED**: нужен
|
||||
variant-level pass, в этом отчёте whole-loader deletion отвергнута.
|
||||
- **Guarantees at risk:** точность, rollback неполного reconciliation,
|
||||
absence ≠ empty, unchanged coverage вне окна и connection fencing.
|
||||
- **Counterarguments / trade-offs:** часть direct force tests относится к old
|
||||
command variants; это не делает всю suite или transactional primitive legacy.
|
||||
Default method caller в `read:121–123` — положительное доказательство жизни.
|
||||
- **Verification or rollback:** при любой variant deletion оставить ordinary
|
||||
period, reopen, exact decimals, closed empty и failed publication cases;
|
||||
isolated SQLite proof, не mock транзакции. Сейчас deletion не требуется.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-002, DEAD-007, DEAD-011.
|
||||
|
||||
### DEAD-011 — Shared source CAS, identity и FK защищают живые use cases
|
||||
|
||||
- **Decision:** KEEP.
|
||||
- **Severity:** P1.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:64–87,164–225,545–579`; `instruments/infrastructure/catalog-discovery.repository.ts:154–179,184–249,280–339,466–487`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182–239`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:264–283`; `backend/prisma/schema.prisma:148–229,232–262`. Укороченные instruments пути относительны `backend/src/modules/investments/`.
|
||||
- **Claim:** общая `CatalogDiscoveryRepository` не является legacy repository
|
||||
целиком. `reserve/finish/publishSource`, attempt/initiator fences и instrument
|
||||
identities остаются runtime-required после removal discovery producer.
|
||||
- **Current mechanism / runtime consumer:** hourly `events.refresh → readSources`
|
||||
запускает enrichment source work; candles.ensureDetail делает то же для
|
||||
dependency. Enrichment.reserve/drain обращается в shared repository, где
|
||||
transaction проверяет origin и observer attempt до публикации public payload.
|
||||
Live portfolio owner также вызывает metadata.request для своих positions.
|
||||
- **Impact / actual cost:** защита от late/superseded/invalid-token publication
|
||||
и сохранение data relationships; **Impact unmeasured**. Удаление ради меньшего
|
||||
числа проверок может изменить чужую source revision/timestamp; удаление
|
||||
instrument identity каскадирует source/candle/coverage rows.
|
||||
- **Why now:** это важная граница closure DEAD-002: method `publish` exclusive
|
||||
старому discovery producer, method `publishSource` — живому enrichment.
|
||||
- **Minimum change:** сохранять shared owner, registration, transaction/CAS,
|
||||
identity и schema. Code-only deletion может затронуть только exclusive
|
||||
discovery writer `publish`, а не весь repository или таблицы.
|
||||
- **Guarantees at risk:** connection isolation, conditional publication,
|
||||
invalid-token boundary, identity reuse, candle data и PII-safe projection.
|
||||
- **Counterarguments / trade-offs:** online-only reference requirement не
|
||||
делает DB identity disposable. Название repository и один прежний caller
|
||||
не определяют его реальную область ответственности. Data migration, если
|
||||
потребуется, должна быть совместимой и отдельно доказанной.
|
||||
- **Verification or rollback:** targeted source/candle persistence и late
|
||||
connection-switch proof при cleanup; перед destructive migration —
|
||||
disposable backup/restore и FK проверка. В этом аудите DB не открывалась.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** DEAD-001, DEAD-002, DEAD-004, DEAD-007, DEAD-009, DEAD-010.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | IDs | Подтверждённая граница |
|
||||
| --- | --- | --- |
|
||||
| DELETE | DEAD-001 | Только manual writer/reservation/progress/normalization closure; status и live owners сохраняются. |
|
||||
| DELETE | DEAD-002 | Только persisted discovery producer и exclusive repository publish; shared source repository и observe reader сохраняются. |
|
||||
| DELETE | DEAD-003 | Только Accepted DTO; status screen schema сохраняется. |
|
||||
| DELETE | DEAD-004 | Только frontend observe callback/transport key rewrite; auth/access/refetch и backend API сохраняются. |
|
||||
| SIMPLIFY | DEAD-005 | Scope/returned command facade убрать; timer/current resources/cleanup сохранить. |
|
||||
| KEEP | DEAD-008 | Live Ledger planner, history и interval boundaries. |
|
||||
| KEEP | DEAD-009 | Registered hourly scheduler, durable work и lifecycle. |
|
||||
| KEEP | DEAD-010 | Candle first-range load, staging, rollback и coverage. |
|
||||
| KEEP | DEAD-011 | Shared enrichment repository, CAS, identity/FK и metadata source owner. |
|
||||
| INVESTIGATE | DEAD-006 | Public status GET/old screen rows: consumers/retirement неизвестны. |
|
||||
| INVESTIGATE | DEAD-007 | Public backend observe/persisted reference readers: transition и безопасная data граница неизвестны. |
|
||||
|
||||
FIX не назначен: correctness живых путей не превращалась в параллельный аудит.
|
||||
|
||||
### 5.1. Special result — Reachability Ledger
|
||||
|
||||
`confirmed dead` означает отсутствие функционального production consumer в
|
||||
проверенном source graph; import metadata registration при этом может исполняться.
|
||||
`only-test reachable` означает реальный тестовый вызов при отсутствии production
|
||||
entrypoint. External-use неизвестность всегда указана отдельно.
|
||||
|
||||
| Candidate | Reachability evidence: declaration → exports/registration → callers → runtime entrypoints → external accessibility | Dynamic/external risks | Affected tests/docs/config | Minimum safe deletion | Rollback | Confidence |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| C01 — `TbankSyncService.request/dispatch`; **confirmed dead**; DEAD-001 | Declaration `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78`; DI `tbank-sync.module.ts:24`, только class construction → controller calls `status:40`, command callers 0 → no method decorator/timer → no command route, только GET. Все короткие пути в backend sync directory. | DI не auto-dispatch-ит methods; module exports только scheduler. Alternate computed/reflection/script invocation не найден. | Normalize tests C02; controller/status DTO остаются; intended boundary `specs/openspec/specs/tbank-data-sync/spec.md:9–25`. | Writer closure, не service целиком. | Code-only revert. | High |
|
||||
| C02 — `normalizeScreen`, repo screen writers; **only-test reachable**; DEAD-001 | `backend/src/modules/investments/tbank-sync/tbank-screen.ts:9–11` export → import в dead service `:28,55` → 9 test call lines → no runtime entrypoint → не public HTTP validator текущего GET. Repo `reserve:62–109/progress:135–169` callers только request/dispatch. | `InvestmentScreenDto` жив через Swagger/status; нельзя удалить его по association. Helpers не имеют top-level work/timer. | `tbank-screen.spec.ts:1–75` только manual descriptor behavior; status schema tests сохранить. | Helper/test file и exclusive repo methods удалить после C01. | Вернуть closure. | High |
|
||||
| C03 — `CatalogDiscoveryService.request/refresh/drain`; **only-test reachable + dead-parent reachable**; DEAD-002 | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:340–496` → provider/API registration `instrument-sources.module.ts:18–21`, `instruments.api.ts:2` → public methods search/detail/byTicker, но online return или observe=true reader → HTTP не попадает в producer; dead manual refresh `tbank-sync.service.ts:197`. | Живые exported provider/HTTP reader не равны live producer. No lifecycle writer hook; setImmediate только внутри недоступного request. | Direct service cases `backend/test/tbank-catalog-discovery.e2e-spec.ts:85–322`; update factory/selection checks, reader compatibility. | Producer state/methods + exclusive repo `publish`, оставить shared `reserve/finish/publishSource`. | Code-only revert, rows/schema untouched. | High |
|
||||
| C04 — `TbankSyncAcceptedDto`; **confirmed dead export**; DEAD-003 | `backend/src/modules/investments/tbank-sync/tbank-sync.dto.ts:212–218` → export with decorators → 0 consumers/ApiExtraModels → no POST response metadata → отсутствует в обоих известных OpenAPI artifacts. | Class decorators исполняются на import, но не создают route. Shared `investmentScreenSchema` используется Status. Fresh parity не запускалась. | `backend/test/openapi-contract.e2e-spec.ts:30–55`; no Accepted-only test/config consumer. | Только orphan class. | Вернуть class. | High |
|
||||
| C05 — frontend `observe/observationKey`; **only-test reachable**; DEAD-004 | `frontend/src/shared/api/resource-query.ts:71,104,144` → hook public index `src/shared/api/index.ts:12–15` → tests `resource-query.test.tsx:63,167` → production pages используют refetch → private SPA, не public package export. | Dynamic router imports и stories не используют callback; backend wire mode остаётся reachable C09. | Два observe scenarios; сохранить successful-null/refetch/access tests. Convention data `:61–63` — intended old flow, не runtime. | Callback/helper/exclusive imports; safe race proof перевести на живой caller при необходимости. | Вернуть code/tests. | High |
|
||||
| C06 — `_scope` и returned screen facade; **live timer / only-test commands**; DEAD-005 | `frontend/src/features/tbank-connection/model/screen-sync.ts:15–22,37–61` → feature `index.ts:1` → 8 VM call sites → все calls expression statements в lazy routed pages → callback exposure вне SPA не найден. | Удаление всего hook опасно: `:28–35` timer жив. Typed resource fields не означают usage после facade removal. | `screen-sync.test.tsx:22–80` direct commands; all page callers. | Только scope/unused commands/status/fields; live effect сохранить. | Вернуть facade/call signatures. | High |
|
||||
| C07 — `planInvestmentScreen/accountHistory/ledgerRange`; **runtime required**; DEAD-008 | `backend/src/modules/investments/investment-sync-plan.ts:67,287,301` exports → operations source imports → `brokerage-operations.service.ts:80,120,203`, sync service `:106` → Account Ledger HTTP → real public read capability. | Частично legacy vocabulary не делает файл orphan. No DI требуется чистым exported functions. | `investment-sync-plan.spec.ts:100–134`, interval/ledger cases; historical screen cases отдельно проверить после C01. | Whole-file deletion запрещён; variant narrowing требует proof. | Not applicable: KEEP. | High |
|
||||
| C08 — status GET и screen reader; **externally reachable/unknown + legacy documented contract**; DEAD-006 | `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:313–391` → `@Get(':id')` → controller `:40` → guarded HTTP + snapshot `frontend/src/shared/api/spec/openapi.json:5102–5185` → любой authorized consumer с work ID. | Local SPA calls 0; external consumers/old rows UNVERIFIED. Reflection не нужна: route явный. | Status DTO, screen discriminator, repository read/progress types, contract tests, old DB work. | Пока deletion запрещён; отдельное compatibility решение. | Вернуть route/schema; data не удалять. | High reachability |
|
||||
| C09 — catalog `observe` reader branches; **externally reachable + legacy documented contract**; DEAD-007 | `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:84–141,168–217,251–293` → stock/bond catalog services → controller query.sync → runtime HTTP `?sync=observe` → snapshot query enum и docs compatibility. | External use/retirement UNVERIFIED. Pub DTO decorators и router сохраняют вход без frontend observe. | Legacy reference schemas/read tests, shared DTO, persisted data; online requirement `online-instrument-information/spec.md:8–17`. | Сначала consumer transition; не удалить общую storage identity/все query modes. | Code routing revert; data restore потребует backup. | High reachability |
|
||||
| C10 — hourly scheduler; **runtime required**; DEAD-009 | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:33–56` → providers/root import → Nest `onModuleInit` → interval.tick + setImmediate.walk → personal source owners, а не внешняя command API. | Lifecycle/reflection registration — positive entrypoint. Env feature switch не найден; single-process deployment `compose.yaml:34`. | Hourly resource DTO/repo + mapper/source tests, hourly spec; scheduler stubs не доказательство смерти. | KEEP provider/root import/durable hourly rows. | Not applicable: KEEP. | High |
|
||||
| C11 — candle request/staging; **runtime required**; DEAD-010 | `backend/src/modules/investments/instruments/application/candle.service.ts:101–123,206–261` → source module exports → stock/bond market.history.read → controller candle GET → first missing range source work. | Dead manual force caller не отменяет ordinary read caller. Variant-level force нужность UNVERIFIED. | `backend/test/tbank-candles-persistence.e2e-spec.ts:117–200,486–501`; candle coverage/decimal tests; shared source/FK. | KEEP loader/repository; force variants не удалять без отдельного trace. | Not applicable: KEEP. | High |
|
||||
| C12 — shared source repository/CAS/identity; **runtime required**; DEAD-011 | `backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–179,280–339,466–487` → DI `instrument-sources.module.ts:19` → enrichment request/publishSource/finish → hourly events and candle ensureDetail → resource HTTP consumes snapshots. | Имя `CatalogDiscovery` вводит в заблуждение; FK Cascade на instrument → candles/coverage `backend/prisma/schema.prisma:199,213,227`. Source origin/observer fence — live invariant. | Source/candle persistence, calendar consumers, Prisma schema; data purge не выполнялся. | KEEP shared repo/CAS/identity; exclusive discovery publish отделим C03. | Code rollback; destructive data rollback только backup. | High |
|
||||
|
||||
**Полные production call sites C06:**
|
||||
|
||||
| Page | Exact path:line-range |
|
||||
| --- | --- |
|
||||
| Accounts | `frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:21–24` |
|
||||
| Account | `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84` |
|
||||
| Operations | `frontend/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts:96–99` |
|
||||
| Events | `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:67–75` |
|
||||
| Income | `frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:66–69` |
|
||||
| Bonds | `frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:12–13` |
|
||||
| Stock | `frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:45–55` |
|
||||
| Bond | `frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:45–55` |
|
||||
|
||||
### 5.2. Special result — Deletion Closure Plan
|
||||
|
||||
План предназначен для последующей отдельной реализации. В рамках аудита
|
||||
ни один пункт не применён. Это method-level closure, а не whole-folder purge.
|
||||
|
||||
| Candidate | Reachability evidence | Dynamic/external risks | Affected tests/docs/config | Minimum safe deletion | Rollback | Confidence |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| C01/C02 / DEAD-001 | No request root; normalize/repo writers only under dead writer, Ledger planner live | Status route и old screen rows отдельно C08; service/controller DI сохраняются | `tbank-screen.ts`, `tbank-screen.spec.ts`; service imports/injections/fields; repo reserve/progress; `tbank-sync.module.ts` reviewed, сохраняет hourly. Existing docs hourly model уже не требует command; archived ADR/spec не менять. | Удалить request/dispatch и reservation field; helper/test files; repo screen reserve/progress; exclusive instrument/candle/metadata/portfolio/membership DI service dependencies. Оставить repository/access/operations, runId/startedAt/status и screen intent/progress types reader. | Revert только code/test deletion. | High |
|
||||
| C03 / DEAD-002 | HTTP ordinary online; observe reader read(true); refresh only dead C01 | Source repository жив и backend observe externally reachable | Service imports `randomUUID`, logger, attempt type, missing-revision constant/state; constructor factory `backend/test/tbank-catalog-discovery.e2e-spec.ts:28–35`; producer-only cases; source module remains. Online selection/whitelist cases сохранить/перепривязать. Docs online intended model не требует producer; archives retain. | Удалить Producer/state/request/refresh/drain и request branch read; удалить только repository `publish:341–464`. Убрать exclusive logger constructor/import. Не удалять `CatalogDiscoveryService` provider целиком, shared repository registration, `reserve/finish/publishSource/readSources/workMany`, catalog selection, online client или schema. | Code/test revert; сохранённые reference rows остаются readable. | High |
|
||||
| C04 / DEAD-003 | Declaration only, not in response/ApiExtraModels/artifacts | Status shared screen schema остаётся published | DTO class only; contract test existing. No script/config exclusive to Accepted найден. | Удалить `TbankSyncAcceptedDto:212–218`; сохранить соседние live classes/imports, которыми они пользуются. | Вернуть class. | High |
|
||||
| C05 / DEAD-004 | 2 direct tests, no production observe symbol consumer | Auth/access effects и server query mode сохраняются | Helper/callback/imports/return member; tests `:40–71,139–173`; frontend data convention old observe paragraph requires owner follow-up, not archive deletion. Feature index `useResourceQuery` остаётся. | Удалить only-test observe path; не удалять resource hook или session snapshot implementation, используемую authenticated-query. Guard/cancellation race проверять через оставшийся live boundary. | Вернуть path/tests. | High |
|
||||
| C06 / DEAD-005 | 8 callers ignore return, scope unused | Live timer — dangerous to remove automatically | 8 VM descriptor arguments; screen hook resource type; direct refresh/retry tests; mocked facade `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:15` пересмотреть по новым exports. Feature barrel остаётся. | Убрать scope/facade, narrowing inputs до использованных timer полей; оставить useEffect/ref/interval cleanup. Rename/move/new helper не требуются. | Вернуть signature/facade/caller descriptors. | High |
|
||||
| C08/C09 / DEAD-006/007 | Explicit HTTP GET/query registration | **ОПАСНА автоматическая зачистка:** external compatibility и existing rows UNVERIFIED | Contract/source DTO/read tests и published frontend artifact; old rows и FK; potential API retirement notice | Не удалять до согласованного support/consumer решения. Получить evidence, затем отдельная route/reader closure. Destructive schema/data removal — отдельный совместимый шаг. | Restore routing/schema, не полагаться на revert для удалённых данных. | High reachability; deletion unverified |
|
||||
| C07/C10/C11/C12 / DEAD-008–011 | Positive Ledger/lifecycle/candle/enrichment call traces | **ОПАСНА автоматическая зачистка:** live behavior, generation/CAS, rollback и cascade data loss | Planner/interval, hourly, source/candle/metadata tests; schema; module registration | KEEP; никакой whole-file/whole-folder/schema deletion по совпадению legacy vocabulary. | Не требуется без изменения; data migration отдельно. | High |
|
||||
|
||||
**Важная closure-граница:** убрать injection из TbankSyncService не означает
|
||||
убрать соответствующий provider из TbankModule/PortfolioModule/InstrumentSourcesModule.
|
||||
Наличие status branch и shared repository читается независимо от отсутствия writer.
|
||||
Документальные references закрываются корректировкой текущего описания только
|
||||
при реальном изменении поведения; история ADR/OpenSpec не стирается.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| IDs | Ожидаемый класс экономии | Фактическое измерение | Обязательные регрессии / безопасная граница |
|
||||
| --- | --- | --- | --- |
|
||||
| DEAD-001/002 | Maintenance, cognitive load, navigation/agent context; меньше old producer tests | not measured; нет готового diff, baseline CI timings или token профиля | Не менять public status/observe, source owners, CAS, hourly/candle/ledger; DI compilation + applicable isolated tests при cleanup. Runtime запросы уже недоступных paths не объявлены экономией. |
|
||||
| DEAD-003 | Малое сокращение obsolete API surface | not measured | Status OpenAPI schema unchanged; no whole DTO/schema purge. |
|
||||
| DEAD-004/005 | Maintenance, test surface, caller descriptors и unused facade | not measured | Session/access invalidation/refetch/cached envelope; periodic current-resource read и cleanup. Bundle/render выгода не заявлена. |
|
||||
| DEAD-006/007 | Возможная будущая экономия compatibility maintenance | UNVERIFIED / not measured | Нужны consumer/retirement proof; shared identity/FK сохраняются. |
|
||||
| DEAD-008–011 | Предотвращение ошибочного cleanup и регрессий | not measured | Сохранить финансовые interval/decimal/transaction/generation/lifecycle invariants. |
|
||||
|
||||
**Ponytail-audit shortlist, от крупной closure к малой:**
|
||||
|
||||
- `delete:` manual screen writer/reservation/normalization. Замена: ничего;
|
||||
hourly source owners уже обслуживают текущий сценарий.
|
||||
[`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–311`]
|
||||
- `delete:` persisted discovery producer и exclusive publish. Замена: существующие
|
||||
online paths; compatibility reader сохранить.
|
||||
[`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:340–496`]
|
||||
- `delete:` frontend only-test observe callback/key rewrite. Замена: существующий
|
||||
ordinary refetch.
|
||||
[`frontend/src/shared/api/resource-query.ts:69–127`]
|
||||
- `shrink:` screen scope/returned facade. Замена: оставить только используемый
|
||||
periodic GET effect.
|
||||
[`frontend/src/features/tbank-connection/model/screen-sync.ts:15–61`]
|
||||
- `delete:` orphan Accepted DTO. Замена: ничего.
|
||||
[`backend/src/modules/investments/tbank-sync/tbank-sync.dto.ts:212–218`]
|
||||
|
||||
**net:** сокращение строк **not measured**, dependencies **not assessed**
|
||||
(направление 12). Оценка прежнего аудита `~550` не повторяется; ни процент,
|
||||
ни скорость, ни CI minutes из inspection не выводятся.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
Максимум подтверждённых code cleanup actions — пять, а не искусственно десять.
|
||||
Последовательность учитывает dependency closures и цену ошибки.
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **DEAD-003:** удалить одну orphan class; проверить Status schema.
|
||||
2. **DEAD-004:** убрать only-test frontend observe path с сохранением живых
|
||||
resource/auth guarantees и meaningful race boundary.
|
||||
3. **DEAD-005:** удалить unused scope и facade, не удаляя periodic effect;
|
||||
обновить 8 callers и проверить timer current-resource/unmount.
|
||||
|
||||
### Более крупные closures с тестовым подтверждением
|
||||
|
||||
4. **DEAD-001:** удалить manual writer плюс normalization/repo writer closure;
|
||||
оставить status compatibility и live DI/source owners.
|
||||
5. **DEAD-002:** после DEAD-001 удалить discovery producer и только exclusive
|
||||
repository publish; перепривязать нужные online/shared-source assertions.
|
||||
|
||||
**Решения, требующие compatibility/ADR или test proof:** DEAD-006/007 не являются
|
||||
подтверждёнными шагами DELETE. API owner должен решить external support до
|
||||
withdrawal route/query semantics. Отдельное ADR оправдано, только если меняется
|
||||
сохранённый public contract/support policy; внутренние dead methods нового ADR
|
||||
сами по себе не требуют. Любая data migration отдельно доказывает FK/restore.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
| Тема | Evidence | Рекомендуемый audit ID |
|
||||
| --- | --- | --- |
|
||||
| Timer/refetch policy и disabled queries — живой путь, не DELETE | `frontend/src/features/tbank-connection/model/screen-sync.ts:28–31`; `src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:10–13` | 02 / 07 |
|
||||
| Old observe convention при новых consumers и documented legacy support | `frontend/.ai/conventions/data.md:61–63`; `docs/architecture/http-api.md:50–52` | 15 |
|
||||
| Income read сохранил request-driven membership branch; это достижимое поведение | `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:408–435` | 07 / 13 |
|
||||
| Проверка scheduler failure/time lifecycle нужна независимо от cleanup | `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:93–110,178–218` | 08 / 13 |
|
||||
| Producer-only tests должны быть пересмотрены по текущему entrypoint | `backend/test/tbank-catalog-discovery.e2e-spec.ts:85–118,179–196`; `frontend/src/shared/api/resource-query.test.tsx:139–173` | 08 |
|
||||
|
||||
Это сигналы владельцам, не отдельные findings этих направлений. Дублирование
|
||||
живых mappers, dependencies, generic VM complexity и архивные docs не аудировались.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Текущее состояние | Самый дешёвый proof |
|
||||
| --- | --- | --- |
|
||||
| Есть ли поддерживаемый consumer GET `/tbank-sync/{id}`? | Local SPA consumers 0; actual external use UNVERIFIED. | Owner API/deployment отвечает о clients; существующая обезличенная access-log выборка по route, без запросов к real API. |
|
||||
| Нужны ли старые `screen:` work после перехода? | Writer недостижим, reader публичен; сохранённые DB rows не проверялись. | Aggregate count/age/scope на disposable копии SQLite + agreed support horizon. Не печатать intent/token/account payloads. |
|
||||
| Кто пользуется `sync=observe` именно у справочных routes? | Source/DTO публикуют режим; потребитель может быть вне SPA. | Endpoint-specific consumer list/log sample и контрактное решение; не выводить removal всех modes из удаления frontend callback. |
|
||||
| Можно ли удалить persisted reference storage целиком? | Нет: есть positive live source/metadata reads и cascade candle FK. | Отдельная source/FK карта нужных identities и backup/restore proof; сначала code-only producer cleanup. |
|
||||
| Какие variants planner/candle force останутся после manual deletion? | Owners живы; unused variant removal отдельно UNVERIFIED. | После узкого diff пересчитать callers конкретных variants; current Ledger/candle behavior comparison, не whole-file purge. |
|
||||
| Пройдут ли cleanup DI/schema/regressions? | Изменений приложения нет, tests/build не запускались. | В отдельной implementation task сначала targeted owner/HTTP/isolated persistence checks, затем локальные обязательные gates по affected repos. |
|
||||
|
||||
### 9.1. Проверка полноты и сохранности результата
|
||||
|
||||
- Проверены 9 общих разделов, полная finding schema для всех 11 IDs,
|
||||
12 candidate chains, explicit DI/dynamic/public/external checks,
|
||||
Reachability Ledger и Deletion Closure Plan.
|
||||
- Финальная consolidation table содержит по одной строке на finding;
|
||||
C01/C02 не удваивают actionable count. KEEP/INVESTIGATE не смешаны с DELETE.
|
||||
- Source pointers просмотрены по локальным файлам и строкам; отрицательный
|
||||
поиск дополнен положительными traces/counterexamples, ограничения сохранены.
|
||||
- После записи отчёта повторно проверены SHA/status всех пяти checkout и
|
||||
`git diff --check`; source/config четырёх репозиториев и root tracking чисты.
|
||||
Отчёт проверен отдельно, потому что root `.gitignore` его игнорирует.
|
||||
- Semantic/Boy Scout review ограничен самим report: не добавлены новые helpers,
|
||||
scripts, обязательные workflows или неподтверждённые измерения; source edits
|
||||
отсутствуют. Existing dated audits, archives и references не менялись.
|
||||
|
||||
## Final machine-readable consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| DEAD-001 | DELETE | P2 | High | backend | src/modules/investments/tbank-sync/tbank-sync.service.ts; src/modules/investments/tbank-sync/tbank-screen.ts; src/modules/investments/tbank-sync/tbank-screen.spec.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts | Недоступная manual orchestration и exclusive writer/test closure | M | DEAD-002,DEAD-006,DEAD-008,DEAD-009,DEAD-011 |
|
||||
| DEAD-002 | DELETE | P2 | High | backend | src/modules/investments/instruments/application/catalog-discovery.service.ts; src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts; test/tbank-catalog-discovery.e2e-spec.ts | Producer старого persisted discovery недоступен production; shared readers/writers живы | M | DEAD-001,DEAD-007,DEAD-010,DEAD-011 |
|
||||
| DEAD-003 | DELETE | P3 | High | backend | src/modules/investments/tbank-sync/tbank-sync.dto.ts | Orphan Accepted DTO без wire consumer | S | DEAD-001,DEAD-006 |
|
||||
| DEAD-004 | DELETE | P3 | High | frontend | src/shared/api/resource-query.ts; src/shared/api/resource-query.test.tsx | Observe callback и key rewrite используются только direct tests | S | DEAD-005,DEAD-007,DEAD-011 |
|
||||
| DEAD-005 | SIMPLIFY | P3 | High | frontend | src/features/tbank-connection/model/screen-sync.ts; src/features/tbank-connection/model/screen-sync.test.tsx; src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts; src/pages/account/ui/AccountPage/AccountPage.vm.ts; src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts; src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts; src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts; src/pages/bonds/ui/BondsPage/BondsPage.vm.ts; src/pages/stock/ui/StockPage/StockPage.vm.ts; src/pages/bond/ui/BondPage/BondPage.vm.ts | Scope/returned facade unused; live periodic GET effect сохранить | S | DEAD-004,DEAD-009 |
|
||||
| DEAD-006 | INVESTIGATE | P2 | High | backend | src/modules/investments/tbank-sync/tbank-sync.controller.ts; src/modules/investments/tbank-sync/tbank-sync.service.ts; src/modules/investments/tbank-sync/tbank-sync.dto.ts | Public status GET и old screen rows требуют external compatibility proof | M | DEAD-001,DEAD-003,DEAD-007 |
|
||||
| DEAD-007 | INVESTIGATE | P2 | High | backend | src/modules/investments/instruments/application/catalog-discovery.service.ts; src/modules/investments/investment-resource.dto.ts; src/modules/investments/instruments/stocks/presentation/stock.controller.ts; src/modules/investments/instruments/bonds/presentation/bond.controller.ts | Backend observe readers externally reachable; retirement UNVERIFIED | M | DEAD-002,DEAD-004,DEAD-006,DEAD-010,DEAD-011 |
|
||||
| DEAD-008 | KEEP | P2 | High | backend | src/modules/investments/investment-sync-plan.ts; src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/operations/application/brokerage-operations-sync.service.ts | Ledger использует planner/history/interval helpers | S | DEAD-001,DEAD-009 |
|
||||
| DEAD-009 | KEEP | P2 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/modules/investments/tbank-sync/tbank-sync.module.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts | Nest lifecycle запускает живой hourly owner без frontend | S | DEAD-001,DEAD-005,DEAD-008,DEAD-011 |
|
||||
| DEAD-010 | KEEP | P2 | High | backend | src/modules/investments/instruments/application/candle.service.ts; src/modules/investments/instruments/infrastructure/candle.repository.ts | Ordinary candle read требует first-range loader/staging/coverage | S | DEAD-001,DEAD-002,DEAD-007,DEAD-011 |
|
||||
| DEAD-011 | KEEP | P1 | High | backend | src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts; src/modules/investments/instruments/application/catalog-enrichment.service.ts; prisma/schema.prisma | Shared source CAS/identity live; blanket deletion рискует isolation и candle data | S | DEAD-001,DEAD-002,DEAD-004,DEAD-007,DEAD-009,DEAD-010 |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,494 @@
|
||||
# 06 — Backend Architecture
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Дата:** 2026-10-11. **Область:** ownership доменных инвариантов, application flows, Nest DI, persistence/transactions, scheduler и integration error boundaries.
|
||||
- **Результат:** локальные исправления текущего монолита; оснований вводить CQRS, микросервисы, generic repositories или обязательные interfaces не обнаружено.
|
||||
- **Affected areas:** `backend/src/modules/investments/{portfolio,tbank-sync,operations,income,instruments,brokerage-accounts}`, integration access/error boundaries. Banking/auth преимущественно защищены решениями KEEP.
|
||||
- **Фактический backend stack:** NestJS `12.0.1`, TypeScript `6.0.3`, Prisma/client/SQLite adapter `7.10.0`, better-sqlite3 `13.0.3`, gRPC-js `1.14.5`; источник — `backend/package.json:40–66,104`. SQLite embedded; PrismaService владеет подключением, backend — один modular application. Node pin `v24.21.0` указан в manifest, runtime этой сессией не запускался.
|
||||
- **Исполнитель:** один независимый агент; дополнительные сабагенты не запускались. Отчёты аудитов 05/07 не читались.
|
||||
- **Режим:** исходники/config четырёх самостоятельных repo и root read-only. Единственная запись — этот новый отчёт через `apply_patch`.
|
||||
|
||||
| Repo | Абсолютный путь | HEAD при начале аудита | Начальный `git status --short --untracked-files=all` |
|
||||
|---|---|---|---|
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | пустой |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | пустой |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | пустой |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | пустой |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | пустой |
|
||||
|
||||
### Инструкции, skills и источники
|
||||
|
||||
Задание [06-backend-architecture.md](../audit-prompts/06-backend-architecture.md) прочитано целиком. Перед исследованием каждого repo прочитан его `AGENTS.md`; backend/frontend — также местный `engineering.md`. Для backend активированы architecture, HTTP API, errors, data/money/transactions, integrations, security/auth, testing, observability; для frontend consumers — data/contract/auth/errors. Для specs прочитан `AI_INDEX.md`, затем существующие тематические specs; для docs — README, CONTRIBUTING, текущие объяснения и ADR 019. Root conventions применены только к записи/проверке отчёта.
|
||||
|
||||
`ponytail-audit` загружен **первым** через skill tool и применён к сложности: недоступная orchestration — DELETE, защищающие механизмы — KEEP. Его complexity-pass отделён от correctness-pass. Также применены `codebase-design` (locality/interface/deletion test), `systematic-debugging` (трассировка причины и контрпримеров), `verification-before-completion`. `graphify` загружен только для проверки существующего графа: `graphify-out/graph.json` отсутствует в root/backend, что подтверждено прямым Read; Glob не нашёл граф и в остальных трёх repo. Генерация/обновление/query с записью artifacts не выполнялись; выводы основаны на исходниках.
|
||||
|
||||
`audit-results/technical-audit-2026-10-11.md` прочитан только как реестр гипотез. Все приведённые ниже механизмы/строки/callers проверены заново. Например, тезис «календари никогда не обновляются» уточнён: dividend key меняется вместе с окном, а bond-events key — нет. Новая существенная проверка — выход Ledger backfill за запрошенный интервал и отсутствие hourly metadata у Accounts/Income.
|
||||
|
||||
### Выполненные команды и ограничения
|
||||
|
||||
| Проверка | Команда/инструмент | Результат и предел доказательства |
|
||||
|---|---|---|
|
||||
| Версия и чистота каждого repo | `git rev-parse HEAD && git status --short --untracked-files=all`, отдельно в пяти cwd | SHA выше; начальные status пустые |
|
||||
| Inventory | `rg --files src -g '*.module.ts' \| wc -l`; аналогично controllers/repositories; `rg -c '^model ' prisma/schema.prisma`, cwd backend | 18 module files, 11 controller files, 8 repository files, 21 Prisma models; это footprint, не число изученных use cases или показатель качества |
|
||||
| Публичные artifacts | `git diff --no-index --stat "/Users/ksv741/Project/vibe/WealthLedger/backend/openapi.json" "/Users/ksv741/Project/vibe/WealthLedger/frontend/src/shared/api/spec/openapi.json"`, cwd root | exit 0, вывода нет: файлы совпадают. Не доказывает свежесть относительно runtime Swagger |
|
||||
| Source/consumer tracing | Read/Glob/Grep с абсолютными repo dirs; поиск registrations, callers, state transitions, DTO и configured tests | Статические механизмы подтверждены; production outcomes и частота ошибок не измерены |
|
||||
| Библиотечные факты | Context7 `/nestjs/docs.nestjs.com`, `/prisma/web` | Подтверждены default singleton scope и rollback interactive transaction при throw; не выдаются за тест текущего adapter/version |
|
||||
| Итоговые проверки | перечитывание отчёта; diff checks и повтор HEAD/status | Результаты зафиксированы после записи ниже |
|
||||
|
||||
Первичные библиотечные свидетельства Context7: Nest [provider scopes](https://github.com/nestjs/docs.nestjs.com/blob/master/content/fundamentals/provider-scopes.md) и [Prisma integration/lifecycle](https://github.com/nestjs/docs.nestjs.com/blob/master/content/data/prisma.md); Prisma [interactive transaction commit/rollback semantics](https://github.com/prisma/web/blob/main/apps/blog/content/blog/wnip-q4-2022-f66prwkjx72s/index.mdx). Документация подтверждает общую semantics, а не concurrency/stop outcome текущего приложения.
|
||||
|
||||
Никакие Node-команды, тесты, install, build, full-suite/verify, codegen, migrations, запуск app, финансовые live API, commit/PR/staging не выполнялись. Тестовые файлы читались как описание regression surfaces, **не как свежий PASS**. Безопасный существующий тест не запускался: для установленных ветвей достаточно кода, а значимые DB/lifecycle сценарии требуют изолированного HTTP/SQLite runner с запуском приложения, запрещённого этой сессией. При последующей проверке Node обязателен местный `nvm use --silent`.
|
||||
|
||||
Узкие поиски в больших деревьях иногда возвращали усечённый список; важные owners затем читались адресно. Ошибочные предположения об именах `app.config.ts`, `ncr.client.ts`, `portfolio-value-history.service.ts` не использованы как evidence: конфигурация найдена в `main.ts`, NCR — в `ncr-ratings.client.ts`; value-history write проверен непосредственно в PortfolioRepository/schema. Игнорируемый OpenAPI прочитан прямо, без доверия глобальному Glob.
|
||||
|
||||
**Финальная целостность:** проверка содержания, counts, точных source ranges и относительных ссылок выполнена; результаты Git-проверок приведены в §9.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт / P1 — BE-001:** `ForecastRepository.resolve` возвращает пересекающийся старый forecast до проверки нового candidate. Сдвиг окна способен удерживать старые финансовые суммы после успешного обновления. Сам CAS/dependency fence правильный и подлежит KEEP.
|
||||
2. **Факт / P1 — BE-002:** верхний catch hourly walk не регистрирует сбой; settlement errors подавляются, а polling durable child rows не ограничен. Persistence failure может дать ложный `succeeded` либо прекратить дальнейшие hourly обновления этого подключения в текущем процессе.
|
||||
3. **Факт / P2 — BE-003/004:** новый schedule-driven owner сосуществует с прежними правилами: изменяемые календари считаются stable, Income GET запрашивает membership, Ledger запускает backfill за пределами выбранного интервала. Это конкретные ownership-разрывы, а не нехватка слоёв.
|
||||
4. **Факт / P2 — BE-005/008:** новые online paths обходят existing invalid-token translator; Accounts/Income не передают durable hourly attempt в resource `meta`. Ошибка и наблюдаемое состояние теряют смысл на application boundary, хотя общая безопасная HTTP boundary есть.
|
||||
5. **KEEP:** banking intent fingerprints/tombstones, версии/refund transactions, refresh CAS, decimal strings/bigint, connection revision и snapshot/dependency fences защищают реальные сценарии. Их удаление удешевит код за счёт корректности.
|
||||
6. **INVESTIGATE:** публикация прогнозов из GET, семантика отсутствующего MOEX block и завершение detached jobs при shutdown требуют адресного proof/решения. Эти пункты не считаются подтверждёнными actionable defects.
|
||||
7. **Стоимость:** основной cleanup — удалить недоступный screen-command path (BE-007); затем исправить существующих владельцев. Runtime/CI/token savings не измерены; никакие проценты или прогнозы ускорения не заявляются.
|
||||
|
||||
**Actionable:** 8 findings: **P0 = 0, P1 = 2, P2 = 5, P3 = 1**. По решениям: FIX = 7, DELETE = 1, SIMPLIFY = 0. Отдельно **KEEP = 4**, **INVESTIGATE = 3**. Severity KEEP отражает важность сохраняемой гарантии, а не существующий дефект. Confidence High означает подтверждённый механизм по current source, не выполненное runtime-воспроизведение.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### Source, schema и DI inventory
|
||||
|
||||
| Область | Что реально исследовано | Ownership / ограничения |
|
||||
|---|---|---|
|
||||
| Composition | `src/app.module.ts:18–38`, все 18 местных module registrations, `src/main.ts:19–42`, Prisma/logging lifecycle | Config, Prisma и logging — shared infrastructure; бизнес-модули подключены в одном процессе |
|
||||
| Auth | controller, Credentials DTO, AuthService полностью, JWT strategy, configured concurrent refresh test | DTO/email policy отдельно от session CAS и cookie boundary; logout изучен как альтернативный entrypoint |
|
||||
| Banking | controller, create/update DTO, card/operation/category services, input, RUB rules, repository, summary use case, relevant SQLite cases | Все mutation entrypoints traced; manual-card spec прочитан по финансовым/ownership/replay требованиям. Полный UI аудит не выполнялся |
|
||||
| Investments persistence | Все 8 concrete repositories; Prisma schema целиком | 21 model: User/Session, 3 banking models, 16 T-Bank models. Подсчёт по `^model `; группы установлены по именам схемы |
|
||||
| Source-driven updates | membership, portfolio, operations, catalog/enrichment, metadata, candles, hourly coordinator | Проверены reserve → RPC → staging/publication → settlement, процессные maps/sets/promises и revision fences |
|
||||
| Reads/projections | Accounts, Portfolio, Ledger, Income, events; stock/bond catalog/income; screener как контрольный пример snapshot | Персональные DTO, `data:null`, complete/empty, timestamps, dependent forecasts, mapping boundaries |
|
||||
| Integrations | TbankClient полностью, TbankAccess/Token/Membership, metadata owner, MoexClient полностью, NCR client | Deadline/retry/auth mapping; no real upstream calls. Глубокий threat model и измерение fan-out вне 06 |
|
||||
| Public contract/consumers | оба предоставленных OpenAPI, выбранные generated schema ranges; Portfolio/Events/Income/bank mutation consumers, shared frontend transport | Связь реального caller с backend behaviour; полная route/schema parity без codegen не проверялась |
|
||||
| Intended behaviour | specs `user-authentication`, `manual-bank-cards`, `hourly-investment-sync`, `tbank-data-sync`, `online-instrument-information`, `brokerage-account-upcoming-events`; docs ADR 019, containers/deployment | Requirements отделены от mechanism. Исторические/параллельные аудиты не являются authority |
|
||||
|
||||
Критическая schema partition: `prisma/schema.prisma:25–71,101–146,232–263` связывает private accounts/portfolios/operations/forecasts/work со `(userId,revision)` и cascade. Public instrument/source/candle данные ключуются provider/UID (`148–230`); personal holdings/estimates туда не должны попадать. Value-history intentionally переживает replacement (`73–99`), но visibility определяется подтверждённым membership. Banking FK — Restrict, replay data и tombstones сохраняются (`278–334`). Payload JSON остаётся внутренним форматом с mappingVersion; comprehensive historical payload migration не проверялась.
|
||||
|
||||
### DI и shared mutable state
|
||||
|
||||
- `TbankModule:10–27` единожды регистрирует client, access, token, membership, metadata/repository; consuming modules импортируют module. `InstrumentSourcesModule:12–23` единожды владеет catalog/candle providers. `TbankSyncRepositoryModule:5–9` отделяет durable storage от coordinator и предотвращает обратную зависимость Portfolio → Sync orchestration.
|
||||
- `PortfolioModule:12–23`, `OperationsModule:11–21`, `UpcomingEventsModule:11–17`, `TbankSyncModule:13–27` образуют явную композицию. `TbankConnectionModule:9–14` импортирует sync module, но его controller/service не вызывают `tick`; первый запуск фактически предлагает minute timer, не PUT callback. Точная гарантия «немедленно» требует уточнения (§9).
|
||||
- `InstrumentsModule:23–33` использует `useExisting: TbankAccessService`, а не создаёт второй access owner. Mandatory ports/interfaces не нужны: concrete injection сохраняет единый limiter и state. Production `forwardRef`, request/transient scopes, manual `new` этих services в просмотренных исходниках не найдены; найденные `new AuthService/PortfolioService` находятся в unit specs.
|
||||
- Stateful owners: Portfolio/Membership pending maps и serialized reservations; Operations ready/runningAttempts; Catalog/Candles/Metadata producers/observers/queues; Screener bounded snapshots; TbankClient FIFO limiter; NCR LRU of shared public registry promises. Их lifetime default singleton, подтверждённый Nest docs. Shared mutation не означает race автоматически: короткая reservation chain и DB attempt fence имеют реальные назначения.
|
||||
- Controllers преимущественно ограничены identity/DTO/cookie/делегацией. В `StockController:85–99` дополнительный whitelist raw body — transport policy, не доменный расчёт. `BankCardsRepository.card:62–67` знает archive/HTTP outcome — существующий cohesive transaction helper; extraction ради «чистого repository» лишь размножит pass-through и не рекомендуется.
|
||||
- Преобразования внешних records в Stock/Bond/Portfolio/Money DTO необходимы: currency normalization, nano/units, nullable money, ticker/UID, unknown values, filtering. Две копии `publicInstrument` — отдельный локальный complexity signal 02, не причина убирать whitelist целиком.
|
||||
|
||||
### Backend Flow Atlas
|
||||
|
||||
Read/write в таблице означает local DB read/write, а не внешнюю финансовую операцию. В изученных flows нет исполнения торговых/банковских переводов в upstream: manual transfer — одна локальная ledger запись.
|
||||
|
||||
| Flow / entrypoint | Chain `request/job → DTO → application → domain/persistence → integration → response/settlement` | Owner, side effects и инварианты |
|
||||
|---|---|---|
|
||||
| Auth refresh | POST `auth/refresh` → cookie extraction, без body DTO → AuthService.refresh → JWT/type/session/hash checks → conditional Session.updateMany → access JWT → controller cookie + 200 | AuthService — session; controller — cookie. R/W session, external integration отсутствует. Один победитель; loser не отзывает winner и не меняет cookie |
|
||||
| Banking mutations | POST/PATCH/DELETE `bank-cards` → runtime DTO/verified `sub` → Card/Operation/CategoryService → normalized input/fingerprint/RUB/date/ownership → BankCardsRepository.write + Prisma transaction → public view / 201,200,204 | Operation service — refund/archive/transfer invariants; DB — atomicity, unique identity/CAS; domain — точные суммы. Нет зависимости от T-Bank |
|
||||
| Portfolio data | GET `:accountId/portfolio`/`all` → verified sub/path → PortfolioService.read/saved → access.local + PortfolioRepository.read + metadata.savedMany → currency totals/structure → `{data,meta}`; job counterpart PortfolioService.request → reserve → tokenForAttempt → GetPortfolio/GetPositions → mapper → publish/history → work | GET локален. Source owner отдельно публикует complete snapshot и value-history в одной transaction; unknown metadata не блокирует финансовый GET |
|
||||
| Hourly sync | native minute tick → DB connections → reserveHourly → membership → portfolios всех account → текущий месячный operations tail → calendars → events.get → settleHourly → resource attempt metadata | Coordinator owns cadence/lifecycle, source services own datasets. Никакой общей transaction вокруг сети/всего walk; section success сохраняется отдельно. Ошибки lifecycle — BE-002/006 |
|
||||
| Forecast publication | GET events или hourly events step → strict date DTO/default UTC window → access/portfolio/readSources → exact quantity × calendar money → ForecastService.result → ForecastRepository.resolve dependency transaction → stored payload/filter/metadata | Publication fence — repository; candidate calculation — UpcomingEventsService; GET/job оба могут записывать forecast. Window selection — BE-001; граница ownership — BE-013 |
|
||||
| Online instrument discovery | GET stocks/bonds/search/ticker/detail/company → query/UUID/ticker DTO → Stock/BondCatalogService/Company use case → CatalogDiscovery/Enrichment online branch → access.onlineToken → TbankClient search/detail/prices → public projection/DTO → `{data,meta}` | Normal online branch не пишет long-lived catalog и не меняет personal snapshots. `observe` остаётся альтернативным persisted read. Auth error mapping — BE-005 |
|
||||
|
||||
#### Две ветки и состояние после ошибки
|
||||
|
||||
**F1. Auth refresh.** `backend/src/modules/auth/presentation/auth.controller.ts:81–91,113–138` → `application/auth.service.ts:81–114,166–201`. Success: проверенный refresh JWT и hash, одно atomic update, новый jti/7-day session, access 15m; cookie устанавливается только после результата. Invalid/expired/superseded: 401; hash mismatch ничего не пишет; expired DB session может удаляться, cookie не очищается. Storage lookup/update failure: 500 safe envelope, controller не успевает set-cookie. Реальная SQLite barrier surface существует в `backend/test/app.e2e-spec.ts:161–213`; не запускалась. CAS отдельно от полномочий JWT strategy; account authorization нельзя заменить guard. Сопутствующее создание session в register/login не является общей transaction с User: failure после создания User оставит зарегистрированный email; login — существующий recovery, runtime такого отказа не проверен и P1 из него не выводится.
|
||||
|
||||
**F2. Banking.** `bank-cards/presentation/bank-cards.controller.ts:94–166,195–203` → соответствующие services. Create replay сначала проверяет owner/fingerprint/tombstone, только новое намерение проходит mutable date/archive/refund validation. Success transfer — одна operation с source/target; баланс derived, отдельного cached balance write нет. Refund сначала CAS-lock исходного expense version, затем aggregate limit и insert/update. Throw после lock откатывает и version, и запись; retry повторяет весь callback, не внешнюю операцию. PATCH использует `version`; DELETE сохраняет tombstone, повтор DELETE no-op, POST replay deleted — 410. Foreign link/archived card/date/limit/version conflict не оставляют половину transfer/refund. Evidence: `bank-operations.service.ts:94–240,251–330`, `bank-cards.service.ts:74–154`, `bank-cards.repository.ts:35–73`; regression surfaces `test/bank-cards.e2e-spec.ts:391–486,622–708` прочитаны.
|
||||
|
||||
**F3. Portfolio.** Local `saved` читает current revision/access и coherent DB snapshot; `all` требует наличие обязательных account snapshots и не выдаёт partial positions за complete (`portfolio.service.ts:55–122,170–231`). Success source attempt: reserve short transaction → два независимых RPC вне transaction → normalize → `PortfolioRepository.publish:116–185` claims attempt и пишет snapshot/history. После network failure прежний snapshot/fetchedAt остаётся, work failed; invalid token помечает connection invalid через access. Replacement deletes private rows/work; поздний publish не проходит attempt/revision claim. Metadata requests запускаются после финансового publish и не являются его atomic dependency. Если этот постпубликационный шаг падает, snapshot уже сохранён; fail не перепишет complete work — scheduler может не показать metadata problem. Это согласуется с optional-label характером metadata, но не доказывает полноту календаря.
|
||||
|
||||
**F4. Hourly.** `tbank-hourly.scheduler.ts:48–172` и `tbank-sync.repository.ts:174–242`: timer only proposes, same-process running row запрещает overlap. Первый проход повторно подтверждает membership; accounts sequentially получают Portfolio, monthly operations, event sources. Ordinary source outage обычно фиксируется durable child work и превращается в partial/failed; другие successful sections не откатываются. Invalid membership token прекращает обработку, connection unavailable. Replacement/deletion удаляет rows; старый attempt settlement становится no-op. Unexpected throw, failure to settle child или parent и superseded child отличаются от обычного upstream outage: текущие handlers могут потерять terminal state/ошибку (BE-002). При restart другой processRunId позволяет interrupt/re-reserve, но это не recovery в текущем процессе.
|
||||
|
||||
**F5. Forecast.** `upcoming-events.service.ts:54–173,200–239` формирует candidate только когда есть полный Portfolio, calendar completeness и identity/ticker. Unknown payouts — null, offer без придуманной суммы; totals по currency. `forecast.service.ts:35–65` передаёт expected portfolio/source versions, `forecast.repository.ts:35–175` проверяет их в transaction. При missing calendar/version race возвращается old compatible fallback либо null без partial publication. Однако fallback поиск по intersecting request window удерживает old candidate, а disjoint request при отсутствии candidate теряет old result (BE-001). Success publication и GET filtering сейчас находятся в одном call; scheduler вызывает его только для каждого account, `all` и instrument payout forecasts могут создаваться из read path — BE-013. Non-happy path с **новым отсутствующим** calendar действительно сохраняет forecast (`test/upcoming-events.e2e-spec.ts:294–318`); это не proof того, что уже сохранённый calendar после failed forced refresh всегда исключён из нового candidate.
|
||||
|
||||
**F6. Online.** Production stock/bond controller default `sync !== observe` → `CatalogDiscoveryService.detail/byTicker/onlineSearch:149–165,227–249,295–337`; company → `CatalogEnrichmentService.section:241–267`. Success record → public whitelist → domain-specific DTO; financial snapshots не перезаписываются. No match/null detail: not found; exact ticker ambiguity: conflict; deadline/queue/upstream outage: 503 TBANK_UNAVAILABLE; auth rejection: plain Error доходит до 500, а stored invalid flag даёт правильный 400 — BE-005. Search Promise.all reject не возвращает частичный список; сохранённые personal datasets не затронуты. Persisted `observe` читает старые records и не выполняет online branch; удаление compatibility без проверки consumers не предлагается.
|
||||
|
||||
### Transaction & Consistency Boundary Map
|
||||
|
||||
| Инвариант / владелец | Atomic boundary и реальные entrypoints | Сеть, error/retry и возможность обхода |
|
||||
|---|---|---|
|
||||
| Refresh rotation — AuthService | Session conditional `updateMany` по id+old hash; refresh controller, Session schema `265–276` | JWT signing до/после CAS, не network transaction. Superseded hash получает 401 без revoke. Проверка logout race/revocation policy относится 13 |
|
||||
| Bank intent/CAS/refunds — services + repository | `BankCardsRepository.write` охватывает replay lookup, owner, source-version lock, refund sum, insert/update/delete и public view; create/update/delete card/op/category | До write нормализуется immutable intent; mutable checks в transaction. Throw rollback; bounded busy retry повторяет callback целиком. DTO alone не владеет финансовым инвариантом |
|
||||
| Connection replacement — ConnectionService | PUT validateToken **до** DB; delete old + create revision/membership/accounts + history visibility в одной transaction (`tbank-connection.service.ts:31–88`) | Upstream/encryption/transaction failure сохраняет прежнюю connection. DELETE идемпотентно скрывает history; успешный replacement даже тем же token создаёт revision |
|
||||
| Membership — TbankAccessService | claim current attempt, account reconciliation, membershipRevision/At, history visibility и completeWork (`tbank-access.service.ts:148–238`) | RPC вне transaction; invalidToken mutation fenced по attempt/revision. Source caller — MembershipService; privileged put создаёт новый membership в отдельной корректной boundary |
|
||||
| Portfolio/history — PortfolioRepository | claim + snapshot upsert + per-currency attempt-key history points (`116–185`) | RPC завершены; ошибка transaction не оставляет complete-work без snapshot. History only fully valued; late/superseded attempt no-op |
|
||||
| Operations — OperationsRepository | page staging `602–699`; complete marker/identity checks; group publication+coverage+operationsRevision+advanceWork `703–881` | RPC page вне transaction. Atomic reconciliation не публикует subset; first missing intervals могут complete отдельно. Broker IDs deduplicate; cursor continuation validated. Wide request planning — BE-004 |
|
||||
| Shared source/candles — Catalog/Candle repositories | publication claims initiator+observer (`catalog-discovery.repository.ts:154–278`), candle staged range completeness and replacement (`candle.repository.ts:137–301`) | Общая public row не содержит holdings/token error; work remains private. Network failure сохраняет прежнюю source row; failure финализации ломает lifecycle, не atomic DB data |
|
||||
| Forecast — ForecastRepository | connection/membership fence + expected portfolio/source revision/timestamp checks + payload upsert (`35–175`) | Calculation вне transaction, versions rechecked внутри. Window lookup до fence проверки candidate — BE-001. Same generic owner имеет 3 production consumers: events, stock dividends, bond coupons |
|
||||
| Hourly lifecycle — SyncRepository/Scheduler | reservation transaction; отдельный conditional settlement по id/attempt/user/revision/state (`174–242`) | Walk не ACID целиком, это правильно. Current-process stalled child rows/parent settlement нуждаются в BE-002; no distributed lease оправдан только single-process assumption |
|
||||
|
||||
### Source / request / schedule ownership и альтернативные entrypoints
|
||||
|
||||
| Ресурс | Intended owner | Current callers и обход |
|
||||
|---|---|---|
|
||||
| Accounts/membership | connection validation + hourly refresh | PUT и MembershipService/hourly оправданы; Income `collectRanges` дополнительно вызывает membership — BE-004 |
|
||||
| Personal Portfolio | hourly source service | GET Portfolio/Accounts/positions local; `PortfolioService.request` callers hourly и недоступный screen command. Manual command удалить, source method сохранить |
|
||||
| Operations tail/history | hourly mutable tail; только missing Ledger interval — request exception | `readPlan` также backfills remaining account history; Income вызывает его для comparison ranges — BE-004. `planOperationRead` уже не переобновляет complete interval: этот guard сохранить |
|
||||
| Event calendars | hourly future sources | `events.refresh → readMany(false) → requestMany(force=false)` делает stable reuse — BE-003. Stock/bond payout GET имеют другие ranges/source kinds; это не доказательство freshness hourly calendar |
|
||||
| Forecast | complete source publication + saved read/filter | GET и job входят в один `result/resolve`; no separate attempt contract. Не вводить новую очередь/CQRS, сначала проверить BE-013 |
|
||||
| Instrument detail/search/company | online request | Default path online, persisted observe compatibility; финансовый GET использует только saved labels. Внешний auth boundary пропущен — BE-005 |
|
||||
| Candles | впервые отсутствующий диапазон | CandleService.read local coverage → только gap request; native source service staged publication. Completed history reuses coverage; detailed tail policy не является performance аудитом |
|
||||
| Screener pages | explicit POST snapshot → local GET page | `stock-screener.service.ts:65–109,131–139`: bounded snapshot/user/revision/TTL guards оправданы; page не перечитывает Shares |
|
||||
|
||||
### Integration Error Mapping Matrix
|
||||
|
||||
| Источник / condition | Integration outcome | Application/job mapping → HTTP / durable state | Retryability и состояние данных |
|
||||
|---|---|---|---|
|
||||
| DTO invalid | configured ValidationPipe BadRequest | filter → 400 VALIDATION_ERROR + pointers (`main.ts:31–40`, filter `24–47,67–81`) | До mutation; target/value исключены, internals не выдаются |
|
||||
| Domain banking version/refund/archive/intent | helpers BadRequest/Conflict/NotFound/Gone | 400/409/404/410 BANK_*; controller не делает собственный envelope | Immutable intent retry безопасен; failed transaction rollback. Frontend mutation retry false |
|
||||
| Refresh invalid/expired/superseded | AuthService Unauthorized | 401 UNAUTHORIZED; login invalid credentials отдельный code | Cookie untouched на failed refresh; no application retry loop |
|
||||
| No T-Bank connection / stored invalid flag | access.local/current/onlineToken | 409 TBANK_NOT_CONNECTED / 400 INVALID_TBANK_TOKEN (`tbank-access.service.ts:124–145,262–288`) | Permanent; no RPC; private read unavailable |
|
||||
| gRPC auth rejected | `Error(INVALID_TBANK_TOKEN)` (`tbank.client.ts:553–557`) | PUT translator → 400; durable owners → failed work + invalid connection; online Discovery/Company/Screener → **500 INTERNAL_SERVER_ERROR** (BE-005) | Client RPC не retry auth. Frontend GET видит 500 и считает transient; personal snapshots не изменяются online path |
|
||||
| gRPC deadline/resource exhausted/unavailable | one retry then ServiceUnavailable | 503 TBANK_UNAVAILABLE; jobs обычно store same failure category | Bounded client retry; duplicate nested retry не нужен; old complete snapshots остаются |
|
||||
| Local RPC queue full | ServiceUnavailable TBANK_UNAVAILABLE | 503 или failed child work | Не повторяется внутри TbankClient (`504–566`); scheduler persistence recovery отдельный вопрос |
|
||||
| gRPC NOT_FOUND / empty detail | shareBy/getInstrumentBy special 404; absent instrument null | Discovery detail → 404; metadata lookup stores supported absence | `bondBy` не включён в special 404 branch (`559–565`), потому raw NOT_FOUND там станет 503; нужен synthetic parity case, не вывод о реальном upstream contract |
|
||||
| Ticker ambiguity | selected >1 exact matches | 409 STOCK_TICKER_AMBIGUOUS / BOND_TICKER_AMBIGUOUS | Не temporary outage; не publishing financial absence |
|
||||
| MOEX 404 | request returns null | rows returns []; optional yield null; events may be complete empty | No HTTP client retry loop; 10s AbortSignal timeout |
|
||||
| MOEX missing block / malformed/cursor | parseRows throws ServiceUnavailable MOEX_UNAVAILABLE | enrichment stores TBANK_UNAVAILABLE; existing calendar preserved | Missing block conflates absence/corruption — BE-014; public safe mapping, loss of provider-specific code in durable child policy не требует нового registry |
|
||||
| NCR invalid ISIN/no match | null | optional rating null | Не outage; shared public registry promise cache |
|
||||
| NCR timeout/parser/empty registry | service-unavailable NCR_UNAVAILABLE | enrichment marks unavailable kind/durable failure | Failed promise TTL 60s, не false no-rating; personal finance неизменны |
|
||||
| DB/unexpected exception | throw / callback rollback | HTTP filter → 500 safe code; background owners log safe event | Correct envelope не чинит missing terminal state. Hourly catch/settle silence — BE-002 |
|
||||
|
||||
Matrix основана на current source: `TbankClient:504–588`, `TbankTokenService:26–35`, `CatalogEnrichmentService:535–590`, `MoexClient:119–198`, `NcrRatingsClient:28–57,64–126`, `HttpExceptionFilter:19–65`. В ответах не обнаружен прямой raw stack/SQL/token output в изученных flows; это не comprehensive security certification.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### BE-001 — Request window участвует в идентичности forecast и блокирует новую публикацию
|
||||
|
||||
- **Decision:** FIX. **Severity:** P1. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79,80–174`; caller `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173,271–279`; scheduled caller `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–175`; filtering `backend/src/modules/investments/portfolio/application/forecast.service.ts:98–120`. Intended: `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:12–24,63–76`. Real consumer: `frontend/src/entities/brokerage-event/api/useBrokerageEvents.ts:7–23`.
|
||||
- **Claim:** новый полный candidate для сдвинувшегося окна может не публиковаться, хотя все источники получены; disjoint read без candidate не возвращает требуемый saved empty result.
|
||||
- **Current mechanism:** exact key включает account/from/to. Если exact previous отсутствует, repository ищет первый intersecting forecast без ordering и выполняет `if (!previous && fallback) return fallback` **до** candidate dependency validation/upsert. Default UTC from меняется ежедневно. При no candidate и непересекающемся запросе fallback отсутствует.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Механизм способен удерживать старые projected amounts/calculatedAt/window и исключать новые события после successful hourly cycle. P1 обоснован систематическим блокированием финансовой publication boundary, не размером файла; фактический ущерб/частота не измерены.
|
||||
- **Why now:** hourly owner рассчитывает ежедневно меняющееся default window; это обычный live path, а не только искусственный caller. Existing test `backend/test/upcoming-events.e2e-spec.ts:321–331` закрепляет disjoint `data:null`, не актуальное requirement.
|
||||
- **Minimum change:** в existing owner разделить выбор последнего complete forecast данного account от HTTP window filtering; fallback допускается при missing/incompatible candidate, но не должен veto нового полного candidate. Выбор latest deterministic. Сохранить исходное окно в record/response; wire-поле окна согласовать с 05. При необходимости изменить key — additive migration/backfill с сохранением старых records до proof, не delete/reset JSON history.
|
||||
- **Guarantees at risk:** membership/account scope, portfolio/source revision recheck, original calculatedAt/window, complete-only publication, failure fallback и all-account completeness.
|
||||
- **Counterarguments / trade-offs:** early return сохраняет timestamp при narrow GET — полезная гарантия (`test/upcoming-events.e2e-spec.ts:619–641`). Простое удаление line 79 позволит narrow GET создать другой прогноз; поэтому один-line removal недостаточен. «Самый новый» выбирается внутри current revision/membership, не глобально.
|
||||
- **Verification or rollback:** isolated SQLite existing upcoming-events suite: два full candidates с соседними default windows и изменённой quantity/calendar; второй становится latest; narrow/disjoint GET сохраняют его identity и original window; dependency race, account removal, source failure сохраняют fallback. Three ForecastService consumers (events/stock dividends/bond coupons) проверить отдельно. Backup/additive schema позволяет code rollback без утраты старых forecasts; DI providers не добавлять.
|
||||
- **Effort:** M. **Related finding IDs:** BE-003, BE-012, BE-013, BE-014.
|
||||
|
||||
### BE-002 — Hourly lifecycle может терять ошибку или навсегда ждать orphan child work
|
||||
|
||||
- **Decision:** FIX. **Severity:** P1. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:93–110,178–218`; `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:189–201,223–242,274–286`; producer failure `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:563–590`; Operations sibling `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:195–230,296–301`; membership sibling `backend/src/integrations/tbank/tbank-membership.service.ts:36–44,53–68`.
|
||||
- **Claim:** нештатное завершение walk может обозначаться success; transient DB failure может оставить durable child/parent running и исключить будущие hourly attempts в текущем процессе.
|
||||
- **Current mechanism:** верхний catch повторно вызывает settle с прежним `failures`; пустой список → succeeded. Settlement reject подавляется. Catalog outer catch очищает active/queue/completions без durable finish оставшихся rows. Scheduler polling ждёт, пока raw row перестанет быть running, без deadline/shutdown/attempt fence; reserveHourly same-process running row всегда возвращает null. Operations step+fail reject тоже очищает process activity без гарантии failed row. Любое non-running child принимается terminal, но raw superseded не разрешается к successor.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** После одного persistence fault последующие обновления всех секций этого connection могут остановиться до restart/replacement, либо metadata сообщит succeeded без полного прохождения. Это серьёзный механизм P1; upstream outage обычно корректно обрабатывается и сам по себе не доказывает stall.
|
||||
- **Why now:** lifecycle — единственный плановый owner personal data; корректная DB atomicity не помогает scheduler, если он не узнаёт terminal outcome.
|
||||
- **Minimum change:** у coordinator различать completion/error/interruption и записывать safe section/cause при outer failure; bounded child wait с expected attempt/successor semantics; после bounded persistence recovery логировать невозможность durable settlement и разрешать диагностируемое восстановление stale same-process work. Использовать существующие rows/logger; не заменять их распределённой очередью. Проверить все source finalizers, не ограничиться scheduler catch.
|
||||
- **Guarantees at risk:** no overlapping walk, no early retry failed attempt, current connection isolation, independent successful section results, no false complete and no raw sensitive logs.
|
||||
- **Counterarguments / trade-offs:** restart другой processRunId восстанавливает eligibility; ошибки могут быть редкими. Но исправная single-process система должна переживать transient write failure без обязательного operator restart. Deadline не должен отменять legitimate long work и немедленно запускать overlapping replacement; re-reservation нуждается в fence.
|
||||
- **Verification or rollback:** controlled-time coordinator test + real disposable SQLite: отказ `finish/publishSource` после reservation, последующая DB recovery; wait завершается diagnostic failed/partial, next eligible walk допускается, late old publish rejected. Также throw до refreshAccounts, settle failure, connection deletion, operations supersession. Existing `upcoming-events.e2e-spec.ts:140–148` scheduler заменяет stub; это не proof. Изменение state semantics без schema migration, preserve rows; rollback code возможен, но не восстанавливает остановленные attempts автоматически.
|
||||
- **Effort:** M. **Related finding IDs:** BE-006, BE-008, BE-012, BE-015.
|
||||
|
||||
### BE-003 — Hourly обновление календарей использует stable reuse вместо refresh policy
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–175`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182–239`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:100–125,184–194,596–625,679–692`. Intended: `specs/openspec/specs/hourly-investment-sync/spec.md:8–9`; downstream ForecastService caller — `upcoming-events.service.ts:160–173`.
|
||||
- **Claim:** успешно сохранённые event sources могут не перечитываться при очередном hourly cycle, хотя их содержание изменяемо.
|
||||
- **Current mechanism:** refresh вызывает readMany с observe=false, который передаёт requestMany force=false; policy refresh разрешает только quote kinds. Bond-events — stable с постоянным rangeKey; dividends — stable в пределах exact window. Смена dividend window создаёт новый key и может загрузить его заново; это важный контрпример к формулировке «никогда».
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Bond calendar может бесконечно сохранять старые объявленные/изменённые события; повторные same-window dividend passes игнорируют исправления. Hourly success не означает новое чтение calendar.
|
||||
- **Why now:** этот путь называется refresh и используется actual hourly owner; PUT/membership/portfolio force=true не распространяется на calendars.
|
||||
- **Minimum change:** scheduled event owner должен передать existing force для календарных kinds либо назначить им явную bounded freshness. Stable detail metadata оставить reusable. Перед forced refresh обеспечить сохранение предыдущего forecast на failed calendar, см. BE-013; GET не должен превращаться в refresh command.
|
||||
- **Guarantees at risk:** old complete calendar/forecast survives outage, no personal read RPC, shared source producer isolation, monthly operations/candle history unchanged.
|
||||
- **Counterarguments / trade-offs:** дополнительный upstream budget и rate-limit contention; changed-window dividends уже обновляются. Force всех detail kinds избыточен; ограничить календарями. Объём RPC/latency передан 07, здесь не измерен.
|
||||
- **Verification or rollback:** два successive scheduled passes для одной bond UID и одного dividend window с отличающимся upstream content; timestamps/data/events действительно меняются. Fail второго pass сохраняет прежние rows/forecast и показывает проблему. Local GET не вызывает RPC. DI/schema change не нужен; rollback policy в existing owner.
|
||||
- **Effort:** S. **Related finding IDs:** BE-001, BE-002, BE-013, BE-014.
|
||||
|
||||
### BE-004 — Общий operations read planner расширяет request exception, Income обновляет membership
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** Ledger caller `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:120–151`; Income caller того же файла `391–435`; shared planner `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–126`; HTTP callers `backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–39`, `backend/src/modules/investments/income/income-analytics.controller.ts:24–79`; `backend/src/modules/investments/income/income-analytics.service.ts:45–93`. Intended: `specs/openspec/specs/tbank-data-sync/spec.md:9–22`; actual frontend `frontend/src/entities/income/api/useIncome.ts:14–45`.
|
||||
- **Claim:** обычный Income GET инициирует membership и imports; ограниченный Ledger request инициирует также отсутствующую историю вне выбранного диапазона.
|
||||
- **Current mechanism:** controller default observe=false, consumer не посылает sync. collectRanges делает membership.request и schedule readPlan(history=true). Ledger тоже передаёт history=true; readPlan после requested load вычисляет `accountHistory`, `remaining = uncovered(missing, targets)` и requests их с priority history. Domain plan уже корректно loads only missing, но application расширяет сами targets.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Разные read entrypoints становятся владельцами одной private sync и скрыто расширяют work scope. Это нарушает узкое исключение и увеличивает сложность recovery/supersession. Число RPC и latency не оценены.
|
||||
- **Why now:** ordinary frontend Income query достижим, а текущие tests преимущественно используют observe; Ledger scope-expansion не обнаруживается проверкой одного requested результата.
|
||||
- **Minimum change:** оставить GET Income только local collect/projection; у shared readPlan убрать history-outside-target branch для read callers, сохранить explicit scheduled/request source APIs и только missing requested Ledger intervals. Удалить больше не нужный MembershipService injection из read service после проверки всех callers. Не создавать второй read planner под новым именем. Выбрать cold historical income UX до удаления источника его backfill.
|
||||
- **Guarantees at risk:** first missing Ledger loads permitted data, complete past interval local reuse, monthly tail hourly owner, bounded requested interval, valid cursor/access before any work, independent comparison completeness.
|
||||
- **Counterarguments / trade-offs:** backfill улучшает historical Income availability без ручных действий; отказ может оставить history missing. Это реальная продуктовая цена, но текущая сохранённая policy запрещает hidden full-history work. Решать missing-history ownership явно, а не механически восстанавливать legacy.
|
||||
- **Verification or rollback:** configured HTTP default GET без observe: Income не создаёт membership/operation work; Ledger c narrow past range создаёт intent только внутри range и не outside history. Reopen confirmed interval без RPC; cold/missing comparisons не показывают ложные zero. Caller search ограничить обоими production readPlan paths; DI remove только фактически unused dependency. Code rollback не отменит уже запущенную wide work — regression проверять на disposable DB.
|
||||
- **Effort:** M. **Related finding IDs:** BE-002, BE-007, BE-008, BE-012.
|
||||
|
||||
### BE-005 — Online application paths обходят существующий invalid-token перевод
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/integrations/tbank/tbank.client.ts:553–565`; translator `backend/src/integrations/tbank/tbank-token.service.ts:26–35`; online calls `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:149–165,227–249,295–317`, `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:241–267`; sibling screener `backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:65–81`; HTTP filter `backend/src/common/filters/http-exception.filter.ts:19–31,50–64`; real consumer policy `frontend/src/shared/api/client.ts:162–193`.
|
||||
- **Claim:** неизвестный локально, но rejected upstream broker token в online branch возвращается как internal server error, а не permanent connection failure.
|
||||
- **Current mechanism:** access.onlineToken/current проверяют только stored invalid flag. New gRPC auth rejection становится обычным Error; online services не вызывают translator. Filter правильно скрывает Error как 500 INTERNAL_SERVER_ERROR. PUT и durable owners уже имеют другие корректные routes обработки.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Клиент теряет INVALID_TBANK_TOKEN recovery code; actual shared GET transport относит 500 к transient. Никакое измерение лишних RPC/latency не выполнено.
|
||||
- **Why now:** online-only policy перевела эти endpoints с durable work mapping на прямую request/error boundary; единого error owner здесь не хватает при уже существующем translator.
|
||||
- **Minimum change:** переиспользовать translateInvalidToken вокруг всех прямых online operations, включая screener. Расположить translation у existing application/access boundary; не менять globally plain Error на HttpException без regression durable callers. Нужен один смысл permanent failure, не новый registry.
|
||||
- **Guarantees at risk:** durable invalid-token state/revision fencing, safe envelopes, no auth retry, absence vs unavailable, online failure не меняет private snapshots. Marking connection invalid из online read — отдельное решение с captured revision, не побочный эффект этого минимального fix.
|
||||
- **Counterarguments / trade-offs:** stored invalidToken уже даёт 400 — баг проявляется только при первом external rejection. Глобальный client mapping короче, но сломает существующие string-code catch owners; безопаснее reuse current translator.
|
||||
- **Verification or rollback:** synthetic client auth rejection через configured stock/bond detail/search/ticker/company/screener endpoints → 400 INVALID_TBANK_TOKEN; source attempt по-прежнему failed+invalid flag, temporary unavailable → 503; no financial DB changes. DI использует уже экспортируемый TbankTokenService, не новую реализацию. Code-only rollback.
|
||||
- **Effort:** S. **Related finding IDs:** BE-009, BE-012.
|
||||
|
||||
### BE-006 — Eligibility после длинного walk не пропускает наступивший срок
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:171–218`, caller `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–86`; intended scenario `specs/openspec/specs/hourly-investment-sync/spec.md:8–25`.
|
||||
- **Claim:** после завершения walk, перекрывшего очередной hourly term, следующий minute tick уже eligible, хотя пропущенный term не должен становиться немедленным повтором.
|
||||
- **Current mechanism:** `due = now - createdAt >= hourMs`; running в том же процессе блокируется, но после completion учитывается только минимальный elapsed hour. Walk 10:00 → завершился 11:10 → tick 11:11 eligible; requirement допускает следующий не ранее 12:00.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Более частое чем задано refresh после длительного обхода; changing cadence и retry ownership. Никакой оценки нагрузки из этого не выводится.
|
||||
- **Why now:** reservation — действующий единственный owner cadence. Timer frequency сам по себе не чинит eligibility.
|
||||
- **Minimum change:** вычислять следующий допустимый 60-minute term от attempt start с учётом terms, пропущенных во время работы; завершение/interrupt history не должно сдвигать start base. Сохранить single catch-up после downtime.
|
||||
- **Guarantees at risk:** no overlap, failed attempt no early retry, first new revision eligibility, restart interruption и no replay пропущенных часов.
|
||||
- **Counterarguments / trade-offs:** текущий код удовлетворяет простому «не ранее 60 минут», но не более точному saved scenario. Если deliberate desired policy изменилась, сначала согласовать requirement; doc/script redesign не нужен.
|
||||
- **Verification or rollback:** controlled Date + repository state: short, failed, 10:00–11:10, 10:00–12:10, restart/downtime/new revision, точная граничная eligibility. SQLite reservation race proof отдельно от mocked clock. DI не меняется; formula code rollback.
|
||||
- **Effort:** S. **Related finding IDs:** BE-002, BE-015.
|
||||
|
||||
### BE-007 — Недоступная screen-command orchestration сохраняет прежнего владельца обновления
|
||||
|
||||
- **Decision:** DELETE. **Severity:** P3. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–311`; единственный HTTP consumer `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–41` вызывает только status; registration `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:14–25`; helper `backend/src/modules/investments/tbank-sync/tbank-screen.ts:9`; действующий отдельный consumer plan — `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:120–151`; sourceStatus использует Operations `tbank-sync.service.ts:364–390`.
|
||||
- **Claim:** request/dispatch не имеют production entrypoint в текущем checkout и сохраняют лишний manual refresh owner/DI dependencies.
|
||||
- **Current mechanism:** service инжектирует membership, portfolio, metadata, catalog discovery/enrichment, candles ради недоступного dispatch. Controller публикует только GET status. Поиск refs класса/normalizeScreen/planInvestmentScreen и callers в src не выявил production вызова request; OpenAPI contract test также ожидает отсутствие командного route (`backend/test/openapi-contract.e2e-spec.ts:30`).
|
||||
- **Impact / actual cost:** **Impact unmeasured** для времени сопровождения/agent tokens. Подтверждён candidate block 54–311 = **258 физических строк** (включая blank/comments, метод подсчёта `311 - 54 + 1`); это нижняя граница потенциального удаления, не готовый diff и не прогноз total cleanup.
|
||||
- **Why now:** сохранённая policy запрещает manual personal sync; новый hourly owner уже существует. Удаление снижает вероятность повторного подключения legacy flow.
|
||||
- **Minimum change:** удалить request/dispatch и исключительно связанные imports/fields/normalize helper. Сохранить status/sourceStatus и Operations dependency для observe successor. Не удалять planInvestmentScreen целиком, shared screen DTO wire shape, repository read или public GET compatibility без отдельной проверки consumers.
|
||||
- **Guarantees at risk:** current revision status isolation, source/superseded operations observation, public response compatibility; source publication methods не являются dead только потому, что один их caller удаляется.
|
||||
- **Counterarguments / trade-offs:** внешний dynamic caller или будущий manual refresh не подтверждён. Для возврата feature придётся реализовать новый контракт; это не основание поддерживать отключённую orchestration. Persisted screen rows могут оставаться историческим input status, их schema deletion в эту задачу не входит.
|
||||
- **Verification or rollback:** повтор production caller search; targeted configured status/source tests, DTO/OpenAPI parity; реальный AppModule initialization после DI pruning. TbankSyncModule imports InstrumentSources можно убрать только если больше нет consumers в его providers; Portfolio/UpcomingEvents/Operations для hourly оставить. Code rollback без migration.
|
||||
- **Effort:** S. **Related finding IDs:** BE-004, BE-009.
|
||||
|
||||
### BE-008 — Accounts/Income resource meta не отражает последнюю durable hourly attempt
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** Accounts `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts:38–76`; Income ranges `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:438–445,489–540`; top-level Income `backend/src/modules/investments/income/income-analytics.service.ts:45–70,114–135`; defaults `backend/src/modules/investments/investment-resource.mapper.ts:60–61,91–103`; корректный пример `backend/src/modules/investments/portfolio/application/portfolio.service.ts:223–229`. Intended `specs/openspec/specs/hourly-investment-sync/spec.md:27–28`, `tbank-data-sync/spec.md:24–29`; generated consumer `frontend/src/shared/api/generated/schema.d.ts:940–996`.
|
||||
- **Claim:** поле присутствует в contract, но metadata разных personal resources содержит разные понятия lastAttempt; successful hourly state может превращаться в none у Accounts/Income.
|
||||
- **Current mechanism:** Accounts читает hourly и отдаёт legacy top-level sync, но meta factory получает work=null и не получает lastAttempt → none. Income ranges/top-level не передают hourlyAttempt вообще; fallback строится из source work, timestamps null, failed problems помечаются section membership даже для operation work. После complete work fallback возвращает none, хотя hourly attempt есть.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Consumers, использующие стандартный meta вместо Accounts-only sync, не могут различить absence/succeeded/error history; нарушается единый наблюдаемый lifecycle. Финансовые суммы сами по себе не повреждаются.
|
||||
- **Why now:** schema требует lastAttempt, и реальные Income/Portfolio/Events queries возвращают одинаковую resource envelope. Presence/type parity не обнаруживает semantic omission.
|
||||
- **Minimum change:** передать existing durable hourlyAttempt на Accounts meta и во все Income meta levels с соответствующей operations/account relevance. Reuse helper/repository через имеющуюся module boundary; source work state хранить как техническое наблюдение, не подменять им hourly attempt. Сохранить top-level Accounts sync до consumer migration.
|
||||
- **Guarantees at risk:** current connection privacy, partial independent results, no fake timestamps, source freshness unchanged, no extra personal work on metadata read.
|
||||
- **Counterarguments / trade-offs:** Accounts уже показывает полный state через sync; это оправдывает compatibility, но не неверное meta. Один global partial state может отражать unrelated account issue — определить relevance helper existing policy, не вводить вторую status registry.
|
||||
- **Verification or rollback:** реальные saved hourly none/running/succeeded/partial/failed rows + Accounts/Income/Portfolio GET: lastAttempt state/time/problem scope согласованы; source timestamp/data unchanged. Default Income GET проверять после BE-004; no RPC/work reservation. DI changes ограничить existing repository import/export, schema change не нужен.
|
||||
- **Effort:** S. **Related finding IDs:** BE-002, BE-004, BE-009.
|
||||
|
||||
### BE-009 — KEEP: конкретные modules/providers и простой монолит
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P3. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/app.module.ts:18–38`, `backend/src/integrations/tbank/tbank.module.ts:10–27`, `backend/src/modules/investments/instruments/instrument-sources.module.ts:12–23`, `backend/src/modules/investments/tbank-sync/tbank-sync-repository.module.ts:5–9`, `backend/src/modules/investments/instruments/instruments.module.ts:23–33`; topology statement `docs/architecture/deployment.md:1–8`.
|
||||
- **Claim:** текущая composition уже обеспечивает пригодные к тестированию interfaces и общие singleton owners; mandatory ports/CQRS/distributed scheduler не решают подтверждённые defects.
|
||||
- **Current mechanism:** concrete services injected через один owning module; metadata/candle/source и durable work helper отделены по ответственности; useExisting alias не создаёт второй экземпляр. Controllers делегируют, pure calculations отдельно. Single-process ceiling явно помечен `ponytail:` у hourly scheduler `26–31`.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Сохранение предотвращает дополнительную DI/config/serialization/migration complexity; текущая latency или экономия не измерены.
|
||||
- **Why now:** findings о смешанном ownership легко ошибочно превратить в большой DDD/CQRS rewrite вместо исправления конкретных state transitions.
|
||||
- **Minimum change:** оставить монолит/concrete injection и исправлять текущих owners; prune только недоступные dependencies по BE-007.
|
||||
- **Guarantees at risk:** единственный limiter/cache/pending owner, простой backup SQLite и independent section publication.
|
||||
- **Counterarguments / trade-offs:** horizontal scaling или несколько backend processes потребуют lease/distributed coordination и другой proof. Фактическая deployed replica count не предоставлена; docs не считаются runtime measurement.
|
||||
- **Verification or rollback:** relevant AppModule initialization/HTTP test при DI pruning; deploy manifest от владельца deployment для multi-process решения. Архитектурная миграция сейчас не нужна.
|
||||
- **Effort:** S. **Related finding IDs:** BE-002, BE-007, BE-015.
|
||||
|
||||
### BE-010 — KEEP: banking transactions, versions, intent fingerprints и refresh CAS
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:152–240,251–330`; `backend/src/modules/banking/bank-cards/application/bank-cards.service.ts:74–154`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–73`; `backend/prisma/schema.prisma:278–334`; auth `backend/src/modules/auth/application/auth.service.ts:99–114,166–201`; callers controllers и regression surfaces `backend/test/bank-cards.e2e-spec.ts:391–486,622–708`, `backend/test/app.e2e-spec.ts:161–213`.
|
||||
- **Claim:** SHA fingerprint/version/tombstone — не искусственная сложность: каждый механизм отличает meaningful competing financial/auth outcomes.
|
||||
- **Current mechanism:** replay сначала проверяет original normalized intent, потом возвращает **current** record; tombstone запрещает восстановление deleted operation. CAS/refund source-version lock и whole-callback transaction обеспечивают aggregate limit. Refresh old-hash CAS даёт одного победителя, loser cookie не очищается.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Удаление даст double-entry, stale overwrite, refund overspend либо session winner revocation. Измеренного incidents/maintenance savings нет.
|
||||
- **Why now:** это защищённые invariants для любого cleanup application/repository layering.
|
||||
- **Minimum change:** сохранить механизмы и cohesive transaction boundaries; не переносить checks за transaction и не заменять DB CAS процессным lock.
|
||||
- **Guarantees at risk:** exact once intent, independent concurrent operations, current replay response, no resurrection, refund sum≤expense, rollback source lock, cookie winner.
|
||||
- **Counterarguments / trade-offs:** fingerprint — не security secret; hash identity — не generic idempotency framework. Runtime uniqueness errors нужно интерпретировать конкретно, но second registry/storage не нужен. Existing DB tests прочитаны, не запускались.
|
||||
- **Verification or rollback:** при изменении owner использовать текущие SQLite cases replay/concurrent refunds/rejected lock rollback/stale update/deleted replay и refresh barrier. Code rollback с сохранением schema/tombstones; их purge требует отдельной data migration.
|
||||
- **Effort:** S. **Related finding IDs:** BE-011, BE-012.
|
||||
|
||||
### BE-011 — KEEP: decimal strings, bigint-копейки и meaningful financial mappers
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/common/decimal.ts:19–83`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`; `backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts:63–106,109–139`; `backend/src/modules/investments/income/income-analytics.service.ts:185–198,296–326`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:119–143,282–299`.
|
||||
- **Claim:** эти helpers и mappings выполняют реальные precision/currency/null semantics; замена на JS number или прямой upstream record ухудшит contract.
|
||||
- **Current mechanism:** parsed decimal = bigint+scale, aligned add/subtract/multiply, explicit divide rounding; per-currency aggregation; RUB at most two decimals and signed-64 input boundary; arbitrary exact derived sums; nullable unknown money отделено от known zero.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Guards предотвращают потерю копеек/nano, смешивание currencies и ложные нули. Число avoided incidents и runtime cost не измерены.
|
||||
- **Why now:** flatten application layers не должен flatten anti-corruption financial representation.
|
||||
- **Minimum change:** сохранить helpers и projections у current owners; unify только доказанно одинаковую public whitelist, не money/portfolio mapping с разными semantics.
|
||||
- **Guarantees at risk:** exact input/output, unknown≠zero, bond percentage≠nominal money, currency separation, aggregate без arbitrary input cap.
|
||||
- **Counterarguments / trade-offs:** custom decimal имеет собственную ответственность и tests; generic decimal library migration может быть оправдана новыми операциями, которых сейчас не установлено. Одного number для display percentage не следует трактовать как финансовую persistence model.
|
||||
- **Verification or rollback:** existing decimal/RUB/domain tests и configured boundary values; cross-currency returns, non-integer quantity/nano, negative/zero, RUB overprecision/limits. Замена storage type без migration не допустима.
|
||||
- **Effort:** S. **Related finding IDs:** BE-010, BE-012.
|
||||
|
||||
### BE-012 — KEEP: connection/attempt/dependency fences и staged complete publication
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31–88`; `backend/src/integrations/tbank/tbank-access.service.ts:148–257`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:116–185`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35–49,80–175`; `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:602–699,703–881,932–944`; `backend/src/modules/investments/instruments/infrastructure/candle.repository.ts:193–301`.
|
||||
- **Claim:** conditional publication — необходимая сложность; source result не становится safe только потому, что upstream succeeded.
|
||||
- **Current mechanism:** new connection revision, composite FKs/cascade, attemptId/process owners, claim before publication, exact source/portfolio versions, staging complete markers, work+snapshot+coverage atomic commit. Actual callers — hourly, missing Ledger/candles, forecast consumers и connection PUT/DELETE.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Защищает от late/superseded publication, cross-connection financial leak, incomplete reconciliation и confirmed empty при missing dependencies.
|
||||
- **Why now:** BE-001/002/003/004 меняют выбор/порядок work, поэтому именно эти guards должны оставаться regression surface.
|
||||
- **Minimum change:** сохранять claims и compound predicates; чинить selection/lifecycle рядом с ними, не заменять durable identity проверкой только process memory и не удерживать transaction вокруг RPC.
|
||||
- **Guarantees at risk:** previous complete result and timestamps, fail/late no-op, idempotent attempt history points, all source completeness, rollback stage/result consistency.
|
||||
- **Counterarguments / trade-offs:** есть overlap технических pattern fragments между owners. Generic executor/repository абстракция не доказана: publication scopes/rollback groups различны. Attempt-level fences не гарантируют correct cadence и terminal lifecycle, что видно BE-002.
|
||||
- **Verification or rollback:** real SQLite publication race/supersession/connection replacement/dependency mismatch cases; current `test/upcoming-events.e2e-spec.ts:344–369,643–668` обеспечивает подходящие сценарии для части boundary, но не был выполнен. Schema migration только additive с backup и isolated restore.
|
||||
- **Effort:** S. **Related finding IDs:** BE-001, BE-002, BE-004, BE-010, BE-011, BE-013.
|
||||
|
||||
### BE-013 — INVESTIGATE: один forecast interface объединяет saved read и публикацию
|
||||
|
||||
- **Decision:** INVESTIGATE. **Severity:** P2. **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:54–173`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:18–95`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:24–175`; other production callers `backend/src/modules/investments/instruments/stocks/application/stock-income.service.ts:51–94`, `backend/src/modules/investments/instruments/bonds/application/bond-income.service.ts:82–125`; scheduled event caller `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–175`.
|
||||
- **Claim:** request/job share publication ownership; необходимо выяснить, должен ли local GET после failed/unfinished calendar cycle иметь право публиковать projection по новым holdings и старой complete source row. Сам факт local DB write ещё не доказывает неправильный финансовый outcome.
|
||||
- **Current mechanism:** every GET calculates candidate из saved current portfolio/source data и calls resolve(candidate); repo не проверяет calendar attempt outcome. Stored source completeness остаётся complete после failed force; scheduler проверяет children и сам skips get при failed, но другой HTTP caller этого barrier не имеет. Job публикует только per-account events, не all/instrument payout forecasts.
|
||||
- **Impact / actual cost:** **UNVERIFIED; Impact unmeasured.** Potential relabel of snapshot/calculatedAt после failed source refresh и request-owned all forecast. Достижимость failed **existing** stable calendar из обычного hourly path ограничена BE-003, поэтому это не добавлено в actionable defects.
|
||||
- **Why now:** BE-003 force refresh расширит реальный non-happy path; BE-001 изменит selected candidate. Нужно не создать regression исправлением рядом.
|
||||
- **Minimum change:** сначала existing upcoming-events fixture: full forecast, same UID quantity change, forced same-calendar failure, subsequent normal GET. Если old forecast заменяется вопреки desired contract — split existing read/publish methods, schedule-specific publication, saved all aggregation; без CQRS/new module. Применимость к stock/bond payout ranges решить отдельно.
|
||||
- **Guarantees at risk:** original holdings/calculatedAt/window, failed update retention, all-account complete contributions, optional metadata independence и accurate data:null.
|
||||
- **Counterarguments / trade-offs:** lazy local projection по полным stored dependencies может быть сознательно корректной моделью и проще отдельного materializer. Read-only для сети не обязательно означает zero DB writes. Без product decision о snapshot identity нельзя рекомендовать полный перенос.
|
||||
- **Verification or rollback:** cheap synthetic DB fixture + existing ForecastRepository and GET boundary, no financial network; определить expected output до migration. При split проверить DI exports и все три callers; старые rows/backfill сохранить для reversible code transition.
|
||||
- **Effort:** M. **Related finding IDs:** BE-001, BE-003, BE-012.
|
||||
|
||||
### BE-014 — INVESTIGATE: MOEX missing block и corrupted payload имеют одинаковый смысл
|
||||
|
||||
- **Decision:** INVESTIGATE. **Severity:** P2. **Confidence:** High.
|
||||
- **Evidence:** `backend/src/integrations/moex/moex.client.ts:119–177,182–198`; consumer `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:469–472,535–590`; retained fixture expectations `backend/test/upcoming-events.e2e-spec.ts:540–599`; intended `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:78–84`.
|
||||
- **Claim:** source и saved requirement расходятся: успешный ответ без requested block считается ошибкой, spec считает отсутствием данных. Нужность самой spec semantics не подтверждена upstream contract.
|
||||
- **Current mechanism:** parseRows(undefined) throws MOEX_UNAVAILABLE так же, как wrong columns/row shape; cursor отдельно строго validates complete pagination. Durable layer сохраняет old calendar и failed work.
|
||||
- **Impact / actual cost:** **Impact unmeasured.** Возможен false unavailable вместо confirmed empty; противоположный механический fix способен публиковать false empty вместо incomplete upstream response.
|
||||
- **Why now:** calendar refresh fixes будут регулярно проходить эту boundary; current tests закрепляют conservative interpretation, но не подтверждают сохранённое requirement.
|
||||
- **Minimum change:** согласовать absence contract для каждого запрошенного блока, затем отличить deliberate missing от malformed/cursor truncation в existing parser. До такого proof guards KEEP; no second error registry/client.
|
||||
- **Guarantees at risk:** complete calendar, malformed rejection, cursor termination, previous forecast preservation, absence≠outage.
|
||||
- **Counterarguments / trade-offs:** conservative failure безопаснее false complete empty. Saved spec — requirement evidence, но не доказательство поведения MOEX; network investigation в сессии не выполнялась.
|
||||
- **Verification or rollback:** synthetic existing fixtures undefined block/empty valid block/wrong columns/invalid cursor; approved saved protocol examples или первичная документация MOEX, без financial live calls. Изменение interpretation code-only, existing records not rewritten automatically.
|
||||
- **Effort:** S. **Related finding IDs:** BE-001, BE-003, BE-013.
|
||||
|
||||
### BE-015 — INVESTIGATE: detached source jobs не имеют общей graceful-stop boundary
|
||||
|
||||
- **Decision:** INVESTIGATE. **Severity:** P3. **Confidence:** Medium.
|
||||
- **Evidence:** `backend/src/main.ts:40–42`; `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–54,84–86`; `backend/src/prisma/prisma.service.ts:24–30`; detached drains `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:175–180`, `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:221–225`; gRPC clients created `backend/src/integrations/tbank/tbank.client.ts:190–255`.
|
||||
- **Claim:** остановка timer не равна завершению walk/drains; поведение активных RPC/DB publication при app close/SIGTERM не доказано.
|
||||
- **Current mechanism:** shutdown hooks enabled, Scheduler onModuleDestroy только clearInterval; detached promises не возвращены из hook. Prisma disconnect — отдельный owner. У TbankClient нет module-destroy close implementation. Durable processRunId recovery присутствует, но runtime stop order/grace period не исследованы.
|
||||
- **Impact / actual cost:** **UNVERIFIED; Impact unmeasured.** Возможны interrupted work и диагностический шум при shutdown; data loss из этого не установлена, DB transaction fences сохраняются.
|
||||
- **Why now:** controlled scheduler lifecycle входит в scope; single-process stop/deploy — обычная операция, не hypothetical horizontal scaling.
|
||||
- **Minimum change:** сначала controlled synthetic app-close case с заблокированным RPC; установить, нужен ли bounded drain/cancel и closing flag у existing owner. Не строить общий job framework/lease ради неподтверждённого исхода.
|
||||
- **Guarantees at risk:** no new reservations after stop, bounded shutdown, rollback in-flight transaction, next process recovery, no pending late publish into new revision.
|
||||
- **Counterarguments / trade-offs:** process termination + durable interruption may be accepted design, docs допускают короткий downtime. Добавление await-all без deadline может само повесить shutdown; timer stop alone и app close outcome следует проверить отдельно.
|
||||
- **Verification or rollback:** existing Nest HTTP fixture с synthetic deferred RPC, app.close → release → restart on disposable SQLite; проверить terminal/recoverable rows, отсутствие post-close unexpected rejection. Production grace period/replica config предоставить отдельно. DI stays existing.
|
||||
- **Effort:** M. **Related finding IDs:** BE-002, BE-006, BE-009, BE-012.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| ID | Decision | Предмет решения | Почему это минимально достаточно |
|
||||
|---|---|---|---|
|
||||
| BE-001 | FIX | Forecast selection/window vs publication | Исправить existing owner, сохранить fences и saved filtering |
|
||||
| BE-002 | FIX | Terminal hourly/source work semantics | Current durable rows/logger, bounded recovery вместо новой job platform |
|
||||
| BE-003 | FIX | Mutable calendars in scheduled refresh | Existing force/policy только calendar kinds |
|
||||
| BE-004 | FIX | Income imports и Ledger scope expansion | Убрать скрытых read owners; сохранить narrow missing-load exception |
|
||||
| BE-005 | FIX | Invalid-token online mapping | Existing translator и actual caller sweep |
|
||||
| BE-006 | FIX | Skipped hourly term | Local eligibility formula, не второй timer |
|
||||
| BE-007 | DELETE | Недоступные request/dispatch | Реального production command consumer нет; status/source APIs сохранить |
|
||||
| BE-008 | FIX | Resource-relevant durable attempt metadata | Existing hourlyAttempt, без status registry |
|
||||
| BE-009 | KEEP | Concrete DI/simple modular monolith | Реальные shared owners; недоказанная нужда в распределении |
|
||||
| BE-010 | KEEP | Financial transaction/CAS/replay/tombstones | Предотвращает lost update/refund overflow/resurrection/session winner loss |
|
||||
| BE-011 | KEEP | Exact money and boundary mappers | Meaningful precision/currency/null conversion |
|
||||
| BE-012 | KEEP | Connection/attempt/dependency/staging fences | Late response и complete-only publication |
|
||||
| BE-013 | INVESTIGATE | Request/job forecast publication interface | Product semantics + same-source-failure fixture до переноса |
|
||||
| BE-014 | INVESTIGATE | MOEX missing-block meaning | Не превращать corrupted/incomplete calendar в confirmed empty |
|
||||
| BE-015 | INVESTIGATE | Graceful stop of detached jobs | Runtime proof до нового coordination code |
|
||||
|
||||
**SIMPLIFY:** самостоятельных подтверждённых architecture-level кандидатов нет. Мелкие duplicate mappings/pass-through wrappers переданы 02; отсутствие SIMPLIFY не означает обязанность наполнить таблицу.
|
||||
|
||||
Complexity-pass по фактическому формату `ponytail-audit`:
|
||||
|
||||
- `delete:` убрать недоступные `TbankSyncService.request/dispatch` и исключительно связанные state/imports/normalization; replacement — existing hourly owner плюс retained status reader. [`backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–311`]
|
||||
|
||||
**net: -258 lines, -0 deps possible.** 258 — точный размер подтверждённого candidate block по current line range, а не размер подготовленного diff; дополнительные helper/import deletions не оценены. Ни одной dependency, которую можно удалить вместе только с этим path, не установлено.
|
||||
|
||||
### Что оставить простым монолитом
|
||||
|
||||
Auth, banking и investment source owners должны оставаться в одном Nest application. Transactional банковский сценарий не следует дробить на use-case class для каждого write или оборачивать Prisma в generic port. Shared financial records/forecasts требуют concrete publication guards, а не CQRS шины. Hourly coordinator может остаться native timer + durable reservation + source services; каталог — existing online adapters и narrow saved labels/calendar/candle storage. Самый дешёвый полезный рефакторинг здесь — уменьшение альтернативных владельцев и явные terminal outcomes, а не новые уровни.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
Все размеры эффекта, кроме физического блока BE-007 и footprint inventory, **not measured**. Effort S/M — качественная оценка scope, не часы и не SLA.
|
||||
|
||||
| Действие | Ожидаемый класс benefit | Фактическое измерение | Regression/security boundary и migration cost |
|
||||
|---|---|---|---|
|
||||
| BE-001 latest saved selection | reliability/maintenance/cognitive load | not measured | Narrow/disjoint queries, day shift, all scope, source/portfolio race; возможная additive key/window migration и DTO consumer transition |
|
||||
| BE-002 terminal lifecycle | reliability/operational recovery | not measured | Fault-injected settlement + DB recovery, no overlap, correct cadence, old result retained; same schema feasible |
|
||||
| BE-003 calendar policy | data correctness/freshness | not measured; возможный runtime cost **увеличивается** | Force only calendars, bounded limiter, failed-cycle fallback; RPC budget владельцу 07 |
|
||||
| BE-004 single read ownership | maintenance/cognitive load; возможный runtime benefit | not measured | Cold historical Income UX, exact Ledger range, local confirmed reopen; prune DI only after caller sweep |
|
||||
| BE-005 translation | error recovery/maintenance | not measured | All direct online callers + unchanged durable invalid-token semantics; no new registry |
|
||||
| BE-006 cadence | reliability/predictability | not measured | Long term, failure, restart; formula/code only |
|
||||
| BE-007 deletion | cognitive load/maintenance/agent navigation | 258-line candidate block; actual diff/token/time not measured | Public status retained, no schema purge, DI initialization proof; нет confirmed removable deps |
|
||||
| BE-008 metadata | contract coherence/diagnosability | not measured | Resource-relevant state/time, no data timestamp renewal; preserve top-level sync compatibility |
|
||||
| KEEP mechanisms | avoided regressions/complexity growth | not measured | Remove only with equivalently strong DB/protocol proof; no replacement framework justified |
|
||||
| INVESTIGATE mechanisms | prevent wrong migration | not measured | Один directed fixture/decision для каждой гипотезы до изменения owners |
|
||||
|
||||
CI minutes, endpoint latency, N+1 impact, total LOC reduction, token savings и hardware/storage capacity в этом аудите не измерялись. Из statically visible extra call нельзя выводить percentage performance win.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
Порядок по value/risk/effort; только actionable findings. KEEP/INVESTIGATE не представлены как обязательные исправления.
|
||||
|
||||
### Quick wins с targeted regression proof
|
||||
|
||||
1. **BE-005:** подключить existing invalid-token translator ко всем online callers; один configured HTTP table of outcomes + durable unchanged assertion.
|
||||
2. **BE-008:** дать Accounts/Income correct hourlyAttempt в meta; использовать существующий repository/helper, не менять freshness/data.
|
||||
3. **BE-006:** исправить eligibility после skipped terms; controlled clock plus real reservation proof.
|
||||
4. **BE-007:** удалить недоступный screen request/dispatch, сохранив status/Operations successor и public DTO; DI initialization/contract proof.
|
||||
|
||||
### Решения с SQLite/lifecycle proof и согласованием ownership
|
||||
|
||||
5. **BE-002:** устранить false success и orphan-running stall на coordinator и source finalization boundaries; fault injection с recovery прежде refactor.
|
||||
6. **BE-001:** отделить latest complete forecast от query filtering, сохранять original identity/window; при persistent key/wire change — короткое решение/ADR, additive migration и coordinated consumer proof.
|
||||
7. **BE-004:** сузить shared read planner и сделать Income local; согласовать cold-history recovery/UX, не вводя скрытый full-history owner обратно.
|
||||
8. **BE-003:** изменить calendar refresh policy только после failed-existing-calendar proof (BE-013 как prerequisite investigation); измерение бюджета адресовать 07. Исправление selection и calendar freshness — разные regression outcomes.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **05 API/Data flow:** original forecast window требуется specs, но `backend/src/modules/investments/upcoming-events/upcoming-events.dto.ts:68–80` и actual generated `frontend/src/shared/api/generated/schema.d.ts:1225–1234` поля окна не содержат; согласовать wire/consumer transition вместе с BE-001. Полный contract audit здесь не выполнялся.
|
||||
- **05 API/Data flow:** Accounts `meta.lastAttempt` и top-level `sync` используют разные источники (`brokerage-accounts.service.ts:38–76`); frontend generated `schema.d.ts:940–996` допускает оба. Owning semantic fix — BE-008, audit 05 проверить представление/recovery, не дублировать finding.
|
||||
- **05 API/Data flow:** `auth.dto.ts:43–74` сочетает character MinLength с UTF-8 maximum, тогда как `specs/openspec/specs/user-authentication/spec.md:10` требует byte range; parity validation contract — отдельное направление, не session architecture defect 06.
|
||||
- **07 Performance:** online search details/price ordering `catalog-discovery.service.ts:295–317`, per-row banking view loading `bank-operations.service.ts:41–68,333–365`; посчитать fan-out/DB reads и измерить significance, здесь количественного finding нет.
|
||||
- **07 Performance:** forced mutable calendar refresh (BE-003) и request scope expansion (BE-004) влияют на limiter/budget; representative connections/accounts/instruments нужен для capacity, не для доказательства ownership defect.
|
||||
- **02 Complexity:** duplicate `publicInstrument` в `catalog-discovery.service.ts:499–530` и `catalog-enrichment.service.ts:646–677`, локальные Stock/Bond catalog facades имеют meaningful DTO mapping; не удалять последние по одному caller/размеру.
|
||||
- **08/09 Tests:** `test/upcoming-events.e2e-spec.ts:321–331` ожидает disjoint null, `140–148` подменяет scheduler; проверить соответствие scenarios и добавить real hourly terminal/cadence proof. Тесты не запускались, качество всей suite не оценивалось.
|
||||
- **13 Reliability/Security:** refresh CAS/revoke interleaving и valid access после logout (`auth.service.ts:116–140,183–199`, `jwt.strategy.ts:21–25`) проверить относительно принятой stateless-access policy; не заявляется exploit в 06.
|
||||
- **15 Specs/Docs:** `docs/architecture/containers.md:142–148` описывает request-driven startup recovery, ADR 019 `9–29` — hourly owner; `specs/AI_INDEX.md:15–31` не выводит на hourly/data-sync/online specs. Документальный маршрут расходится; новые conventions не требуются.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Самый дешёвый proof / требуемый input | Что меняет |
|
||||
|---|---|---|
|
||||
| Latest forecast — один saved account identity/window или отдельные query scopes? Как сохранять/агрегировать all? | Desired output для full day-1/day-2/narrow/disjoint/all; existing SQLite fixture без upstream сети | BE-001 migration/key selection и BE-013 GET/publication ownership |
|
||||
| Можно ли после failed existing calendar обновить amounts от нового Portfolio? | Same UID full row + изменённая quantity + forced calendar failure + normal GET; сравнить record/calculatedAt/metadata | BE-013 → FIX только при demonstrated forbidden replacement; prerequisite BE-003 |
|
||||
| Кто загружает missing historical Income после удаления скрытого read backfill? | Product decision: предварительный Ledger или явный ограниченный owner; inspect current cold response fixture | Scope удаления BE-004 и сохранённый user recovery |
|
||||
| Что означает успешный missing MOEX block? | Approved protocol docs/synthetic saved samples: missing vs empty valid vs malformed/cursor | BE-014 → parser FIX либо уточнение requirement; complete-only guarantees сохраняются |
|
||||
| Нужен ли graceful drain, или deliberate restart interruption достаточно? | Controlled deferred RPC/app.close/restart на disposable DB; deployed grace period/replica count от владельца deploy | BE-015 и допустимый local lifecycle fix, no distributed lease по умолчанию |
|
||||
| «Первый обход без ожидания срока» допускает minute tick delay? | `onModuleInit`/PUT timing controlled test + точное ожидание; scheduler `48–50`, connection controller `43–50` | Нужен ли local tick-on-success/start; в actionable не добавлено без timing contract |
|
||||
| Есть ли внешние status/observe consumers? | Backend snapshot route inventory + объявленные внешние consumers; текущий frontend production use inventory недостаточен для external отрицания | Безопасный объём BE-007/legacy compatibility; не удалять public GET автоматически |
|
||||
| Должен ли bondBy NOT_FOUND быть public not-found, как shareBy? | Synthetic TbankClient error matrix + approved upstream protocol contract; никаких real broker calls | Расширение BE-005 mapping без превращения true outage в false absence |
|
||||
|
||||
### Итоговая проверка отчёта и workspace
|
||||
|
||||
- Все 9 разделов и три обязательные карты присутствуют. В 15 карточках имеются все поля задания; IDs последовательны BE-001…BE-015, related IDs существуют.
|
||||
- Actionable counts: FIX 7 + DELETE 1 = 8; severity 2 P1 + 5 P2 + 1 P3; P0 нет. KEEP 4 и INVESTIGATE 3 отдельно. Top actions = 8, без включения нерешённых гипотез.
|
||||
- Relative links проверены по прочитанным существующим targets; source references — workspace-relative точные диапазоны текущего HEAD, а не offsets прежнего аудита. Boy Scout review отчёта: ownership, отсутствие дублирующих performance/security findings, доказательства и контрпримеры проверены; приложение не менялось.
|
||||
- `git diff --no-index --check /dev/null "audit-results/06-backend-architecture-report.md"` (cwd root): **exit 1**, диагностического вывода нет; для no-index это наличие diff нового непустого файла относительно `/dev/null`, whitespace errors не найдены. Exit отдельно проверен через shell `$?`, а не выведен из отсутствия stdout.
|
||||
- `git diff --check && git rev-parse HEAD && git status --short --untracked-files=all`, отдельно в root/backend/frontend/specs/docs: все checks exit 0, status пустые, все пять HEAD совпадают с §1. Root report находится вне nested repos; root tracking allowlist/ignore не менялся. Пустой root status не заменяет просмотр нового игнорируемого отчёта: файл перечитан непосредственно.
|
||||
- Статический контроль карточек через `rg -c` в root: 15 заголовков BE, решений FIX/DELETE/KEEP/INVESTIGATE соответственно 7/1/4/3 и 165 обязательных field-bullets (11 на карточку; Severity/Confidence размещены в Decision bullet, Related IDs — в Effort bullet). Counts/severity и machine-readable rows дополнительно сверены вручную.
|
||||
|
||||
### Машинно-считываемая таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| BE-001 | FIX | P1 | High | backend | src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Пересекающийся saved forecast блокирует новый complete candidate; disjoint fallback теряется | M | BE-003,BE-012,BE-013,BE-014 |
|
||||
| BE-002 | FIX | P1 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts; src/modules/investments/instruments/application/catalog-enrichment.service.ts; src/modules/investments/operations/application/brokerage-operations-sync.service.ts | Persistence failure даёт false success либо running stall до restart/replacement | M | BE-006,BE-008,BE-012,BE-015 |
|
||||
| BE-003 | FIX | P2 | High | backend | src/modules/investments/upcoming-events/upcoming-events.service.ts; src/modules/investments/instruments/application/catalog-enrichment.service.ts | Stable calendar reuse не гарантирует обновления будущих событий hourly owner | S | BE-001,BE-002,BE-013,BE-014 |
|
||||
| BE-004 | FIX | P2 | High | backend | src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/operations/application/brokerage-operations-sync.service.ts; src/modules/investments/income/income-analytics.controller.ts | Income импортирует personal data; Ledger backfill выходит за requested interval | M | BE-002,BE-007,BE-008,BE-012 |
|
||||
| BE-005 | FIX | P2 | High | backend | src/integrations/tbank/tbank-token.service.ts; src/modules/investments/instruments/application/catalog-discovery.service.ts; src/modules/investments/instruments/application/catalog-enrichment.service.ts; src/modules/investments/instruments/stocks/application/stock-screener.service.ts | Online invalid broker token теряет permanent code и становится HTTP 500 | S | BE-009,BE-012 |
|
||||
| BE-006 | FIX | P2 | High | backend | src/modules/investments/tbank-sync/tbank-sync.repository.ts | Длинный walk допускает немедленный запуск после пропущенного hourly term | S | BE-002,BE-015 |
|
||||
| BE-007 | DELETE | P3 | High | backend | src/modules/investments/tbank-sync/tbank-sync.service.ts; src/modules/investments/tbank-sync/tbank-screen.ts; src/modules/investments/tbank-sync/tbank-sync.module.ts | Недоступный manual command сохраняет альтернативную orchestration и DI navigation | S | BE-004,BE-009 |
|
||||
| BE-008 | FIX | P2 | High | backend | src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts; src/modules/investments/operations/application/brokerage-operations.service.ts; src/modules/investments/income/income-analytics.service.ts | Accounts/Income meta не отражает actual durable hourly attempt | S | BE-002,BE-004,BE-009 |
|
||||
| BE-009 | KEEP | P3 | High | backend | src/app.module.ts; src/integrations/tbank/tbank.module.ts; src/modules/investments/instruments/instrument-sources.module.ts | Concrete singleton owners и простой монолит достаточно глубоки для текущей задачи | S | BE-002,BE-007,BE-015 |
|
||||
| BE-010 | KEEP | P2 | High | backend | src/modules/banking/bank-cards/application/bank-operations.service.ts; src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts; src/modules/auth/application/auth.service.ts; prisma/schema.prisma | Transaction/CAS/replay guards сохраняют financial integrity и session winner | S | BE-011,BE-012 |
|
||||
| BE-011 | KEEP | P2 | High | backend | src/common/decimal.ts; src/modules/banking/bank-cards/domain/rub-money.ts; src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts | Exact money/currency/null semantics нельзя заменить прямыми number/upstream records | S | BE-010,BE-012 |
|
||||
| BE-012 | KEEP | P2 | High | backend | src/integrations/tbank/tbank-access.service.ts; src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts; src/modules/investments/instruments/infrastructure/candle.repository.ts | Revision/attempt/dependency fences защищают late и incomplete publication | S | BE-001,BE-002,BE-004,BE-010,BE-011,BE-013 |
|
||||
| BE-013 | INVESTIGATE | P2 | Medium | backend | src/modules/investments/portfolio/application/forecast.service.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts; src/modules/investments/instruments/stocks/application/stock-income.service.ts; src/modules/investments/instruments/bonds/application/bond-income.service.ts | UNVERIFIED: GET и job делят forecast publication при failed existing source | M | BE-001,BE-003,BE-012 |
|
||||
| BE-014 | INVESTIGATE | P2 | High | backend | src/integrations/moex/moex.client.ts; test/upcoming-events.e2e-spec.ts | Missing block трактуется как ошибка вопреки spec; безопасная absence semantics требует proof | S | BE-001,BE-003,BE-013 |
|
||||
| BE-015 | INVESTIGATE | P3 | Medium | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/prisma/prisma.service.ts; src/integrations/tbank/tbank.client.ts | UNVERIFIED: detached jobs не возвращают completion в graceful shutdown boundary | M | BE-002,BE-006,BE-009,BE-012 |
|
||||
@@ -0,0 +1,970 @@
|
||||
# Аудит 07 — API & Data Flow Efficiency
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Дата:** 2026-10-11. **Результат:** независимый статический аудит полного пути
|
||||
React Query → HTTP → Nest → embedded SQLite/Prisma либо T-Bank/MOEX → UI.
|
||||
- Workspace: `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
- Единственное изменение: новый
|
||||
`audit-results/07-api-and-data-flow-efficiency-report.md` в root workspace.
|
||||
Исходники, конфигурации, тесты, public artifacts и история четырёх repo read-only.
|
||||
- Начальные `git rev-parse HEAD && git status --short`:
|
||||
|
||||
| Repo / путь относительно указанного workspace | HEAD | Начальный status |
|
||||
| --- | --- | --- |
|
||||
| root `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean |
|
||||
| `frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean |
|
||||
| `backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean |
|
||||
| `specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean |
|
||||
| `docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean |
|
||||
|
||||
**Инструкции и skills.** В самом начале загружен установленный `ponytail-audit`:
|
||||
complexity-pass ищет минимальное сокращение coordination, а performance/correctness
|
||||
исследуются отдельно по заданию 07. Применены `performance-optimization`,
|
||||
`graphify`, `verification-before-completion`. Прочитаны собственные AGENTS всех
|
||||
repo; frontend engineering/architecture/data/auth/testing/TS/UI, backend
|
||||
engineering/architecture/data/http/integrations/security/testing/errors/observability.
|
||||
Root conventions применены только к итоговому Markdown.
|
||||
|
||||
**Graphify:** существующего `graphify-out/graph.json` не найдено в root и в четырёх
|
||||
repo. Проверены directory listings root/frontend/backend и отдельные repo-local
|
||||
Glob. Попытки Glob по отсутствующим frontend/backend `graphify-out` дали ошибку
|
||||
поиска; последующий поиск от существующих repo roots — `No files found`.
|
||||
Граф, extraction, scripts и дополнительные artifacts не создавались. Все edges
|
||||
ниже подтверждены source trace, а не предполагаемым графом.
|
||||
|
||||
**Источники гипотез:** `technical-audit-2026-10-11.md` прочитан исключительно как
|
||||
реестр направлений проверки. Все выводы ниже заново сверены с callers, source,
|
||||
конфигурацией и intended semantics. Отчёты аудитов 05/06 и другие результаты
|
||||
консолидации не читались.
|
||||
|
||||
**Метод и исполненные проверки:** Read/Glob/Grep с абсолютными repo paths;
|
||||
Git metadata в пяти cwd; byte-for-byte сравнение имеющихся public artifacts:
|
||||
|
||||
```text
|
||||
cwd = /Users/ksv741/Project/vibe/WealthLedger
|
||||
git diff --no-index --exit-code "backend/openapi.json" "frontend/src/shared/api/spec/openapi.json"
|
||||
result = exit 0, output empty
|
||||
```
|
||||
|
||||
Это подтверждает равенство файлов на диске, **не** свежую генерацию runtime schema.
|
||||
Проверены пути operations, bond search и screener в snapshot:
|
||||
`frontend/src/shared/api/spec/openapi.json:2356,3336,4194,6066`.
|
||||
|
||||
**Library semantics:** Context7 `/tanstack/query` подтвердил ручной `refetch` при
|
||||
`enabled: false`, stale mount/focus/reconnect и независимость interval от staleTime;
|
||||
документация также различает автоматический dedup и императивный cancel/refetch.
|
||||
Источники: [disabling queries](https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries),
|
||||
[important defaults](https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults),
|
||||
[useQuery](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery).
|
||||
Установлен Query `5.102.8`, `openapi-react-query 0.5.4`:
|
||||
`frontend/package.json:46,54`. Context7 lookup openapi-react-query не дал точного
|
||||
пакета; вместо подмены другим generator прочитан установленный source
|
||||
`frontend/node_modules/openapi-react-query/src/index.ts:194–219`:
|
||||
key = method/path/init, queryFn передаёт AbortSignal.
|
||||
Context7 `/prisma/web` и официальный transactions URL сейчас выдают Prisma 8,
|
||||
тогда как проект использует `7.10.0` (`backend/package.json:50–51,64`).
|
||||
Version-specific SQL expansion/parallelism **UNVERIFIED**; бюджет ниже считает
|
||||
явные Prisma model calls, не обещает «include = один SQL» и не предлагает API v8.
|
||||
Nest guard/controller/DTO поведение установлено по исходникам, спорной
|
||||
library-specific Nest семантики в recommendations нет.
|
||||
|
||||
**Не запускалось:** приложение/порты, live bank/broker/MOEX/NCR API, install,
|
||||
build, full-suite, codegen, migrations, production benchmark, staging/commit/PR.
|
||||
Тесты не запускались: достаточен static proof механизмов; штатные frontend test
|
||||
scripts запускают build packages, а SQLite suites создают/изменяют БД. Чтение
|
||||
тестов — evidence их intent, не утверждение об их текущем прохождении.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт:** Accounts и Portfolio читаются локально; Events не делает upstream RPC,
|
||||
но может вычислять и публиковать forecast в SQLite на GET. Основной read-model
|
||||
оправдан и должен сохраниться.
|
||||
2. **Подтверждённая граница лишней работы:** обычные Income GET инициируют
|
||||
membership/operations, а Ledger дополнительно планирует отсутствующую историю
|
||||
вне выбранного периода. Это второй владелец работы рядом с hourly walk,
|
||||
FLOW-001. Даже HTTP `sync=observe` у Ledger не является passive переключателем.
|
||||
3. **Подтверждённый fan-out, не измеренная задержка:** online bond search =
|
||||
`1 + N + I(N>0)` logical RPC, `0≤N≤30`; details параллельны между собой,
|
||||
prices ждёт их завершения. Detail-поля действительно нужны карточке;
|
||||
безопаснее сначала убрать независимую waterfall, FLOW-003.
|
||||
4. **Подтверждённые усилители работы:** timer вручную refetch-ит disabled queries
|
||||
(FLOW-002); неизвестный заранее invalid broker token превращается в HTTP 500
|
||||
и повторяется frontend transport до трёх раз (FLOW-004).
|
||||
5. **Факт структуры DB cost:** banking history выполняет `3 + Σ(1+t+s+c+r)`
|
||||
model reads на страницу; embedded SQLite устраняет сетевую DB latency, но не
|
||||
стоимость повторного materialization/queries, FLOW-005.
|
||||
6. **Fresh/consistent risk по коду:** hourly reuse стабильного bond calendar и
|
||||
пересекающийся fallback forecast способны удерживать старый результат после
|
||||
новой попытки, FLOW-006/007. Это не аргумент удалять CAS или complete publication.
|
||||
7. **Непроверенные гипотезы:** общий выбор 30 секунд / 30 минут, полная hydration
|
||||
всех portfolio snapshots и объём income DTO требуют policy/scale evidence,
|
||||
FLOW-008/009. Их нет в confirmed actionable severity.
|
||||
|
||||
**Counts:** actionable **7**: P0=0, P1=0, P2=7, P3=0.
|
||||
Отдельно **KEEP=1**, **INVESTIGATE=2**. Runtime latency, throughput, bytes,
|
||||
production rate exhaustion и ускорения **UNVERIFIED**; для всех findings
|
||||
**Impact unmeasured**. Severity основана на подтверждённом механизме и локальном
|
||||
риске, не на выдуманных p95 или процентах.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Метод подсчёта и область применимости
|
||||
|
||||
Изучены 8 сценариев A–H, включая все пять обязательных. Выборка risk-based:
|
||||
личные reads, online fan-out, cursor/history, аналитика, scheduler/calendar,
|
||||
financial read/write projection и catalog pagination. Это не endpoint census
|
||||
всего приложения и не аудит frontend composition/backend domain style.
|
||||
|
||||
- **HTTP:** один GET/POST/PUT к приложению — один attempt. Query cache hit = 0;
|
||||
shared in-flight Query key = один автоматический fetch, а не число observers.
|
||||
- **RPC:** `TbankClient` method invocation — logical RPC. Transport attempts
|
||||
отдельно: 1–2 при допущенном RPC; queue rejection = 0 сетевых attempts.
|
||||
MOEX HTTP отдельно от gRPC, NCR HTTP отдельно от обоих.
|
||||
- **DB:** считаются явно вызванные Prisma model reads. `$transaction`, BEGIN,
|
||||
COMMIT, relation SQL expansion, disk I/O, hydration time не приравниваются к
|
||||
отдельному удалённому round-trip. Нужен SQL trace для actual SQL count.
|
||||
- Сценарии счёта имеют действующую сессию/connection/membership. JWT validate не
|
||||
читает DB (`backend/src/modules/auth/infrastructure/jwt.strategy.ts:21–25`).
|
||||
Auth bootstrap/refresh, logos CDN и действия пользователя вне одного сценария
|
||||
вынесены за основной бюджет. Ошибки access могут оборвать граф раньше.
|
||||
- «Типовой» ниже означает **явно заданную условную ветку**, не measured median.
|
||||
Произвольная сессия с focus/reconnect/таймерами не имеет конечного общего max.
|
||||
|
||||
Обозначения:
|
||||
|
||||
| Symbol | Значение |
|
||||
| --- | --- |
|
||||
| `A` | число доступных счетов текущего подключения |
|
||||
| `N` | выбранные search results после дедуп ISIN/UID и limit, 0…30 |
|
||||
| `R_T` | недостающие account/month intervals выбранного Ledger/Income scope |
|
||||
| `R_H` | недостающие history intervals вне выбранного scope |
|
||||
| `p_j` | cursor pages interval j, ≥1 при завершённой загрузке даже пустого interval; upper unknown |
|
||||
| `m` | membership logical RPC, 0 или 1 при новом source attempt |
|
||||
| `K` | actual distinct metadata producer calls, 1–2 на producer (UID/FIGI fallback) |
|
||||
| `M(X)` | metadata DB reads: `ceil(U/400)+ceil(F/400)+ceil(V/400)` для distinct UID/FIGI и найденных canonical UID |
|
||||
| `b` | непустые calendar batches: `ceil(S/100)+ceil(B/100)` для unique stock/bond UID в scope |
|
||||
| `J` | дополнительные DB reads awaited source reservation branches; зависит от missing coverage/active work |
|
||||
| `Q_F` | DB reads forecast.resolve на выбранной ветке; формула ниже |
|
||||
|
||||
M не равен количеству positions: identifiers дедуплицируются, matched UID может
|
||||
отсутствовать или быть неоднозначным. Источник: metadata repository `44–112`.
|
||||
|
||||
### 3.2. Статический request graph с actual callers
|
||||
|
||||
```text
|
||||
App QueryClient (focus/reconnect on; Query retries off)
|
||||
→ session-aware query → generated [method,path,init] / AbortSignal
|
||||
→ authenticatedFetch → GET transport: deadline + ≤2 transient retries
|
||||
→ Nest JWT guard / DTO validation / user.sub
|
||||
Accounts → Portfolio.saved → access.local + SQLite snapshots + hourly
|
||||
Portfolio → saved + saved metadata → точная projection → UI
|
||||
Operations → local + readPlan → local page + metadata → UI cursor page
|
||||
↘ missing chosen scope AND history backfill → worker RPC
|
||||
Income → collectRanges → local period union → exact totals/entries → UI
|
||||
↘ membership.request → readPlan(history:true) → worker RPC
|
||||
Events → local Portfolio + saved calendar → forecast.resolve (conditional write)
|
||||
Bonds search → onlineToken → FindInstrument → N BondBy → GetLastPrices
|
||||
→ public projection → card fields → UI
|
||||
Hourly native tick → durable reservation → membership → portfolios → operations
|
||||
→ calendar sources → local forecast calculation/publication
|
||||
```
|
||||
|
||||
Query/transport owners: `frontend/src/app/ui/App.tsx:13–21`,
|
||||
`frontend/src/shared/api/authenticated-query.ts:15–50`,
|
||||
`frontend/src/shared/api/client.ts:53–119,127–151,205–249`.
|
||||
Backend composition inspected in portfolio/operations/income/instruments/
|
||||
instrument-sources/tbank-sync module files; the source providers are shared,
|
||||
not redeclared per consuming module.
|
||||
|
||||
### 3.3. Flow Request Ledger
|
||||
|
||||
Числа относятся к одной успешной попытке queryFn без transport retries;
|
||||
retry multipliers и background work раскрыты отдельно. Cache min=0 требует
|
||||
fresh cache для **всех** relevant keys и отсутствия imperative refetch.
|
||||
|
||||
| Flow / scenario + source | HTTP min / conditional typical / max одной загрузки | Foreground DB reads | Upstream logical / critical path | Conditions / cache / enabled |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **A Accounts list**: AccountsPage.vm `21–24` + AccountsController `19–22` | 0 / 1 cold / 1 | 6 при valid connection | 0 / local DB + DTO | accounts staleTime 1 800 000 ms; shell observers share same key; session required |
|
||||
| **B Account overview/Portfolio**: AccountPage.vm `66–84` | 0 / 4 если accounts fresh, иначе 5 / 5 distinct GET | `6 + (4+M(P)) + (10+M(L)+J) + (7+M(P)+M(S)+3b+Q_F) + income branch` | local Portfolio=0 RPC; total может включать D/E background; foreground GET параллельны | portfolio/operations/events/income enabled accountId; operations limit5; same account key title/breadcrumb dedup |
|
||||
| **C Online bond search**: BondsPage.vm `9–13`, BondController `55–65`, discovery `295–337` | 0 disabled/fresh / 1 valid cold / 1 | 1 onlineToken read | empty=1; nonempty=N+2 (3…32); path Search → slowest Detail → Prices | debounce300ms; trimmed q≥2; Query staleTime30s; backend uppercase normalizes; frontend case-different keys не дедупятся |
|
||||
| **D Brokerage Ledger**: AccountOperationsPage.vm `77–99`, history hook `12–30`, OperationsController `22–38` | 0 / 1 page GET + shared accounts GET если cold / 2 включая shell | `10 + M(page) + J`, cursor ещё +5; SQL expansion unknown | background `Σ p_j`, j∈dedup(R_T∪R_H); foreground не ждёт RPC | page100; backend max1000, take limit+1; filters/categories/cursor server-side; full coverage chosen period не отменяет missing history |
|
||||
| **E Income analytics**: AccountIncomePage.vm `64–69`, IncomeController `66–79` | 0 / 1 analytics + accounts GET если cold / 2 включая shell | observe=6; ordinary fully covered/fresh membership=20 + one write fence; extra `J` при missing work | `m + Σ p_j` over missing period union + outside history; foreground обычно local, membership.done schedule detached | selected/previous/yearAgo ≤3 calendar periods, unlimited duration; frontend НЕ передаёт observe; summary на overview тот же owner с 1 периодом |
|
||||
| **F Hourly refresh + forecast**: scheduler `56–175` | 0 browser requests; 0 Nest HTTP инициаторов | variable reservations/staging/publication; `q` poll iterations → q work reads; finite max unknown | `1+2A+Σp_i+K+D_c+S_c` T-Bank плюс MOEX; sequential owner phases | one process native tick60s, due60min, no overlap reservation; missing/invalid connection gates; detail/dividend/bond-event stable cache |
|
||||
| **G Manual bank card history**: CardPage.vm `35–45`, bank-card queries `76–144` | 0 session disabled / 4 cold or stale mount GET (card/cards/history/summary) / 4; all mode=3+P_h | history `3+Σ(1+t+s+c+r)`; other endpoints separately | 0 RPC; all mode sequential HTTP pages | default staleTime0 revalidates on mount; UI page10; choices5/10/20/50/100/all; backend limit100; all loop100/page until hasNext=false |
|
||||
| **H Stock screener**: StocksPage.vm `29–55,65–101`, StockScreenerService `65–109` | idle0 / explicit search1 POST /1; next page1 GET | search1 current read; next page1 local read | search1 Shares RPC; page0 RPC | outside Query: no Query cache/dedup; snapshot TTL5min/max100/page30/user+revision; GET retry applies only to page, POST not transient retried |
|
||||
|
||||
Paths в строках с сокращёнными именами раскрываются в findings и owner tables.
|
||||
Полные frontend sources flows A–H:
|
||||
`frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:21–24`,
|
||||
`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:66–84`,
|
||||
`frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–13`,
|
||||
`frontend/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts:77–99`,
|
||||
`frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:64–69`,
|
||||
`frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:35–45`,
|
||||
`frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts:29–55,65–101`.
|
||||
Flow F инициируется только
|
||||
`backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:56–175`.
|
||||
Для B сумма DB — foreground endpoints одного холодного overview, а не transaction
|
||||
всего экрана; J/background не измерены. Для D/E общий session max — unknown;
|
||||
invalid cursor может дать дополнительный fetch первой страницы
|
||||
(`frontend/src/entities/broker-operation/model/useBrokerageOperationHistory.ts:32–59`).
|
||||
Для G `P_h=max(1,ceil(T/100))` только при стабильном наборе T операций; при
|
||||
конкурентных изменениях offset dataset точный total requests unknown.
|
||||
|
||||
**Дополнительный detail flow.** BondPage.vm `23–55`: detail → после получения UID
|
||||
quote/metrics/candles/position/coupons/operations (6 dependent queries). Title
|
||||
`frontend/src/app/routes/DocumentTitle/DocumentTitle.tsx:62–66` использует тот же
|
||||
detail key, не дополнительный GET. Для успешной холодной загрузки без redirect
|
||||
7 distinct GET; UID→ticker canonical navigation (`BondPage.tsx:10–12`, VM `62–65`)
|
||||
может добавить новый detail key/GET, итого 8. Detail by UID =1 RPC, by ticker =
|
||||
Search→Detail=2 RPC (`discovery:227–249`). Остальные endpoints могут планировать
|
||||
source workers; их общий count зависит от coverage, calendar/source cache и
|
||||
пагинации, **unknown**, не «7 HTTP = 7 RPC». Quote/metrics разные keys, но
|
||||
overlapping source work coalesces в одном enrichment owner. Online detail не
|
||||
публикуется в shared source cache, поэтому не заменяет cold worker detail.
|
||||
|
||||
### 3.4. Cache & Refresh Ownership Matrix
|
||||
|
||||
| Data/cache | Identity / lifetime | Owner / trigger | Dedup / isolation / замечание |
|
||||
| --- | --- | --- | --- |
|
||||
| frontend accounts | generated method/path; stale30min | account entity; mount/focus/reconnect + screen timer | page/title/breadcrumb/switch разделяют Query; timer только page |
|
||||
| frontend portfolio/ledger/income/events | generated path+account+period/categories/cursor/limit; stale30s | entity + page useScreenSync interval30min | нет дедуп по похожему payload или разному limit; manual refetch игнорирует enabled |
|
||||
| frontend bond/stock detail/search/sections | identifier/period/q в generated key; stale30s | entity, detail-dependent enabled; page timer | case q differs while backend normalizes uppercase; online requests не coalesce на backend |
|
||||
| frontend bank queries | generated key, all mode добавляет `all`; default stale0 | entity; mutations invalidate related subtree | placeholder only same card/period/limit; all mode включает собственный pagination loop |
|
||||
| session/connection cache | Query cache reset/clear, token in memory | auth + connection commit | generated keys без user/revision допустимы только с этой boundary; KEEP |
|
||||
| saved membership/portfolio | user/revision/account; freshness60min | hourly membership force + portfolio force | pending/reservation coalesces; GET local; missing differs from confirmed empty |
|
||||
| operations/coverage | user/revision/account/time/instrument; exact intervals | hourly mutable current-month tail; first missing reads + legacy history | page cursor binds owner/filter/revisions; background missing range dedup; FLOW-001 |
|
||||
| instrument metadata | provider+UID/FIGI lookups; `fresh=complete` | portfolio metadata worker; savedMany reads local | batching400, producer identity shared; no credential/holding in public records |
|
||||
| online search/detail/company | no persistent online result | discovery/section read request | synchronous upstream; no server single-flight shown; online-only requirement, не автоматически defect |
|
||||
| quote/comparison/trading status/bond-current/yield | instrument/source/kind/range; TTL60min | enrichment requestMany quote policy | prices batch up to30, shared source producers; ordinary quote/metrics GET can create work |
|
||||
| dividend/coupon/bond-events/detail/rating source | stable after successful publish; dividends/coupons range key | enrichment + hourly event caller | bonds no calendar range key; dividends hourly exact window changes daily; FLOW-006 |
|
||||
| candles | instrument/range coverage, complete past immutable | first uncovered read; queued gaps, no all-history prefetch | window split, same-scope producer; today tail/quote comparison отдельны; don't remove completeness |
|
||||
| forecast | user/revision/scopeKey incl window; dependency CAS | ForecastService.result from events/coupons GET and hourly get | GET can write; no RPC in events get; full fallback only; FLOW-007 |
|
||||
| screener snapshot | searchId + user/revision, TTL5min, max100 | explicit POST; GET pages readonly | coherent page30; separate from Query; KEEP |
|
||||
| NCR registry | one Promise entry, TTL6h; failure TTL60s | NcrRatingsClient.rating | public registry single-flight + negative failure cache; not personal data |
|
||||
|
||||
Owners/evidence: frontend query files перечислены в ledger;
|
||||
`frontend/src/features/tbank-connection/model/screen-sync.ts:20–51`;
|
||||
`backend/src/integrations/tbank/tbank-source.policy.ts:3–21`;
|
||||
`instrument-metadata.repository.ts:295–329`;
|
||||
`catalog-enrichment.service.ts:178–227,496–579,596–625,679–697`;
|
||||
`candle.service.ts:101–124,264–325`;
|
||||
`stock-screener.service.ts:19–26,84–109,131–138`;
|
||||
`backend/src/integrations/ncr/ncr-ratings.client.ts:12–55`.
|
||||
|
||||
**Duplicate-query вывод:** несколько useBrokerageAccounts consumers не означают
|
||||
несколько HTTP. Title `DocumentTitle.tsx:24–32`, breadcrumbs
|
||||
`AccountBreadcrumbs.tsx:16–25`, switch `AccountScopeSwitch.vm.ts:8–20`, page
|
||||
используют один entity hook. Query promise/cache работает на same key.
|
||||
Императивные timer.refetch, direct screener API, разные limit/period/q и backend
|
||||
source work — за пределами этого автоматического dedup. Нет production
|
||||
hydrate/dehydrate/persistQueryClient пути в просмотренном `frontend/src`;
|
||||
`useResourceQuery:129–148` только проецирует envelope, не создаёт второй cache.
|
||||
|
||||
### 3.5. Upstream call budget table
|
||||
|
||||
| Source / условный сценарий | Logical calls без retry | Network attempts / границы | Total vs critical path |
|
||||
| --- | --- | --- | --- |
|
||||
| Search пустой | 1 FindInstrument | 1…2 если admitted | один RPC |
|
||||
| Search N>0 | `1+N+1`, max32 | до `2(N+2)` при recoverable retry каждого RPC | Search → details cohort → Prices; не N последовательных деталей |
|
||||
| Search transient response/body timeout, browser ≤3 attempts | до `3(N+2)` logical если каждый server attempt дошёл до всех stages | до `6(N+2)`; при N30 условная граница192 | разные server attempts могут перекрываться; это не measured traffic |
|
||||
| Search invalid token на FindInstrument | 1 на backend invocation; browser3 →3 | auth gRPC НЕ retryable | FLOW-004, mapping убирает повтор, не исходный auth check |
|
||||
| Invalid token только на detail cohort | `1+N`, prices не начинается; ≤3(1+N) при трёх HTTP | queue admission/failure может уменьшить count | Promise.all отвергает весь request, уже запущенные sibling details не отменяет |
|
||||
| UID detail / unambiguous ticker detail | 1 / 2 | ≤2 / ≤4 | ticker сначала search, затем detail; consumer действительно требует identity |
|
||||
| Company with assetUid | 1 detail +2 asset calls | ≤6 | detail → parallel(description,fundamentals); absent assetUid=1 |
|
||||
| Hourly successful baseline без new sources | `1 + 2A + Σp_i` | до удвоения T-Bank calls при transient recovery | membership → Σ account portfolio pair(max of2) → Σ operations pages → events |
|
||||
| Hourly new metadata/calendar | добавка `K + D_c + S_c`; K=1…2 на producer; D_c=new instrument detail; S_c=new stock dividend window | ≤2× добавка T-Bank | metadata queue может работать в фоне, enrichment drain сериализует source producers |
|
||||
| Hourly new bond calendar | MOEX `Σ_b(c_b+o_b+1)` HTTP | retry не добавляется; каждая request timeout10s | parallel(coupons pages,offers pages,description), page цепочки последовательны |
|
||||
| Ledger first missing + outside history | `Σ_{j∈dedup(R_T∪R_H)} p_j` | ≤2Σp_j на завершённых admitted pages | worker one page/turn, cursor chain sequential; count не зависит от UI limit5/100 |
|
||||
| Income обычный GET | `m + Σ_{j∈dedup(R_T∪R_H)} p_j` | ≤2(m+Σp_j) за один successful worker pass | membership.done → schedule; response не ждёт worker completion |
|
||||
| Local Accounts/Portfolio/Events GET | 0 T-Bank, 0 MOEX | 0 | local read/DTO; events forecast CAS write возможен |
|
||||
| Screener POST / page GET | 1 Shares /0 | ≤2 /0 | весь online catalog на явное действие; pagination local snapshot |
|
||||
| NCR rating | cache miss1 HTTP; same pending/cache0 | timeout10s, failure cooldown60s | registry shared across ISIN, отдельный от hourly events источник |
|
||||
|
||||
`D_c/S_c` считаются по effective producers после shared cache/coalescing, не по
|
||||
сумме позиций счетов. `c_b/o_b≥1` для нормальных успешных MOEX page walks;
|
||||
без runtime payload верх неизвестен. T-Bank metadata/service details:
|
||||
`tbank.client.ts:261–315,358–448,504–585`;
|
||||
MOEX `moex.client.ts:83–93,119–177`.
|
||||
|
||||
**Retry/deadline budget.** Frontend GET: ≤3 attempts, 15s на fetch **и чтение body**,
|
||||
1s+2s паузы (`client.ts:15–18,127–151,205–249`). Если срабатывают все три
|
||||
deadline, programmed component ≈48s, не guaranteed wall-clock SLA.
|
||||
Query retries globally false. При UNAUTHORIZED возможен один auth refresh и
|
||||
protected replay; консервативная compositional граница ≤7 HTTP attempts
|
||||
(два read phases до3 плюс один refresh), но сочетание зависит от того, какая
|
||||
attempt вернула 401. Сетевые server-side effects не начинаются при auth reject.
|
||||
Known InvalidTbankToken/Forbidden/NotFound не transport retried.
|
||||
|
||||
T-Bank queue starts `r`/second (default5), waiting capacity50, RPC deadline15s
|
||||
назначается **после** queue wait, transient retry один через1s. Queue wait не
|
||||
входит в RPC deadline; default не означает фактический production config.
|
||||
Search32 занимает `ceil(32/r)` rate windows при чистом бюджете; при default r5
|
||||
это7 окон, не обещание7 секунд. Уже есть limiter, добавлять pool/retry вокруг
|
||||
него не нужно. HTTP abort не передаётся controller→TbankClient; client request
|
||||
возвращает Promise, handle не сохраняется для cancel (`tbank.client.ts:571–585`).
|
||||
Поэтому новые HTTP attempts могут выполнять тот же online fan-out, пока старый
|
||||
ещё жив. Actual timeout incidence **UNVERIFIED**. Durable first-load work
|
||||
coalesces; **нельзя** безусловно умножать весь history import на3.
|
||||
|
||||
### 3.6. DB Read Pattern table
|
||||
|
||||
| Path | Source-inferred model-read count | Pattern / limit / interpretation |
|
||||
| --- | --- | --- |
|
||||
| access.local | 1 model call with accounts relation | `tbank-access.service.ts:262–288`; access gate, SQL child queries unknown |
|
||||
| hourly metadata | 2 when valid connection | `tbank-sync.repository.ts:245–259`; current revision + work |
|
||||
| Portfolio.saved | 1 access +1 snapshot relation read +2 hourly =4 | `portfolio.service.ts:55–59`, `portfolio.repository.ts:25–47`; loads ALL account payloads before service filter |
|
||||
| Accounts.list | saved4 + repeated hourly2 =6 | `brokerage-accounts.service.ts:17,39`; projects summaries, no upstream; repeated lookup real, significance unmeasured |
|
||||
| Portfolio.read | `4+M(positions)` | saved metadata batch400; no per-position remote/detail call |
|
||||
| Ledger readPage | 5, plus relation SQL unknown | connection,coverage,work,membershipWork,rows `operations.repository.ts:82–218`; `take limit+1`, stable three-field order |
|
||||
| Ledger foreground | `10+M(page)+J`; cursor extra5 | `operations.service.ts:74,119,145–153,214`; first readPage validates binding before scheduling, preserve validation-before-side-effect |
|
||||
| Income observe | 1 access+5 readPage=6 | exact period union, categories income, no `take`; not a row N+1 |
|
||||
| Income ordinary full warm path | 20 reads +1 updateMany fence, no worker launch | initial access1+page5; membership reserve read1; schedule access1+page5+readPlan(access1+coverage1); final page5; source scheduling J if missing |
|
||||
| Events foreground | `7+M(portfolio positions)+M(supported positions)+3b+Q_F` | get: access1+portfolio4+hourly2; saved metadata again; observe readMany(access/source/work)=3 per batch |
|
||||
| forecast.resolve | no candidate:2+I(fallback lookup); candidate early fallback:1+I(fallback lookup); candidate validation:2+I(fallback lookup)+ceil(sourceVersions/100) reads | write fence/upsert counted separately; `forecast.repository.ts:35–174`; preserve parameter-limit batches and atomic source validation |
|
||||
| Bank history | `3+Σ(1+t+s+c+r)` | card ownership + count +page, then per-row loaders; LIMIT≤100 + offset. Valid income1 / transfer2 / expense3 / refund4 extra reads per row |
|
||||
| Bank all mode | sum history cost over P_h HTTP pages | full hydration deliberately user-selected; no runtime scale bound in frontend all loop |
|
||||
| Screener search/page | 1 /1 model read | selected result stored in process Map, no DB per instrument |
|
||||
|
||||
Ledger base10 assumes selected nonempty intervals: access1 + readPlan access1/
|
||||
coverage1 + readPage5 +hourly2. Empty selected scope skips readPlan and subtracts2.
|
||||
`J` excludes detached worker staging/publish reads. Income ordinary20 assumes
|
||||
membership fresh, targets nonempty, all historical coverage already complete,
|
||||
no connection switch; refresh-launched membership detaches schedule and changes
|
||||
foreground count, so20 is not global minimum/maximum. Metadata formula includes
|
||||
empty and missing identifiers, не максимум3 для произвольного all portfolio.
|
||||
|
||||
No DB latency/SQL query count measured. Embedded database подтверждена
|
||||
`backend/prisma/schema.prisma:7–9`,
|
||||
`backend/src/prisma/prisma.service.ts:16–21` и `env.validation.ts:35–36`.
|
||||
Новые indexes/remote DB/cache tier без EXPLAIN/profile не обоснованы.
|
||||
|
||||
### 3.7. DTO / filtering / privacy / observed metrics
|
||||
|
||||
- Operations фильтрует time/category/instrument на сервере, page bounded;
|
||||
payload содержит financial details сверх compact row UI, но contract ledger
|
||||
требует точную запись (`specs/.../brokerage-account-ledger/spec.md:31–44`).
|
||||
Удалять поля только по одному presentation consumer нельзя.
|
||||
- Income читает union2–3 periods одним page loader без limit и формирует totals,
|
||||
comparisons, buckets, instruments, entries (`income-analytics.service.ts:81–93,138–162`).
|
||||
UI display использует entries/comparisons, не buckets/instruments
|
||||
(`frontend/src/pages/account-income/model/display.ts:97–129`). Однако все выплаты
|
||||
сразу и unlimited period — явное требование spec `10,157–179`; pagination или
|
||||
усечение сумм не являются механическим performance fix, FLOW-009.
|
||||
- Account preview получает весь events dataset и отображает первые5
|
||||
(`frontend/src/pages/account/model/previews.ts:75–96`), тогда как operations
|
||||
уже запрашивает limit5. Полная standalone events page — реальный consumer;
|
||||
новый preview endpoint без bytes/scale evidence не предлагается.
|
||||
- Public DTO projections — trust boundary, а не лишняя копия Prisma rows:
|
||||
`publicInstrument`, `toBond`, `mapBrokerageOperation`, safe error envelope.
|
||||
Shared instrument/calendar cache не содержит token/user holdings; private
|
||||
forecasts, coverage, snapshots и cursors ограничены user/revision/account.
|
||||
- Доступен **исторический synthetic baseline**, не текущий measurement:
|
||||
`backend/test/fixtures/tbank-read-baseline.json:2–18` (immediate upstream,
|
||||
limiter/retries excluded, прежнее состояние persistence). Его numbers не
|
||||
переносились на HEAD. Current benchmark source `test/tbank-read-baseline.e2e-spec.ts:502–541`
|
||||
тоже исключает transport limiter и создаёт artifacts; он не запускался.
|
||||
Текущие production logs/trace и RUM в evidence отсутствуют.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### FLOW-001 — Personal GET сохраняет незапрошенный import/backfill
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M.
|
||||
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:143–151,391–435`;
|
||||
`backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:46–125`;
|
||||
`backend/src/modules/investments/operations/presentation/brokerage-operations.controller.ts:22–38`;
|
||||
`backend/src/modules/investments/operations/presentation/brokerage-operation.dto.ts:36–82`;
|
||||
`backend/src/modules/investments/income/income-analytics.controller.ts:24–79`;
|
||||
`backend/src/modules/investments/investment-sync-plan.ts:287–298`.
|
||||
- **Claim / consumers:** Account overview recent operations+income summary,
|
||||
AccountOperationsPage and AccountIncomePage ordinary GETs can launch work beyond
|
||||
the permitted first missing Ledger interval. Income invokes membership;
|
||||
Ledger also invokes history backfill even with `sync=observe` because its DTO
|
||||
has no sync field and controller не выбирает observe.
|
||||
- **Current mechanism:** both production callers pass `history:true` to
|
||||
`readPlan`; helper computes lifetime history minus shown targets and requests
|
||||
remaining missing intervals at history priority. Unknown opening falls back
|
||||
to1970. Income membership TTL miss can fetch GetAccounts, then detached schedule
|
||||
plans periods and history; no TTL miss still enters reservations/schedule.
|
||||
- **Impact / actual cost:** `Ledger=Σp(R_T∪R_H)` вместо `Σp(R_T)`;
|
||||
`Income=m+Σp(R_T∪R_H)` вместо0 personal RPC. Page limit5/100 не ограничивает
|
||||
backfill месяцы/pages. Полный выбранный период + отсутствующий другой месяц
|
||||
достаточно для лишнего RPC даже при пустом upstream результате. При failures
|
||||
повторные reads могут снова резервировать failed work; active work coalesces,
|
||||
поэтому amplification не равна автоматически3×. **Impact unmeasured.**
|
||||
- **Why now:** сохранённая intended boundary явно ограничивает исключение
|
||||
(`specs/openspec/specs/tbank-data-sync/spec.md:9–22`,
|
||||
`brokerage-account-ledger/spec.md:131–144`); hidden work расходует общий limiter
|
||||
и source очередь в конкуренции с hourly/online search.
|
||||
- **Minimum change:** у Ledger выключить outside-scope history branch; у Income
|
||||
сделать collectRanges чтением независимо от совместимого observe flag.
|
||||
Сохранить Ledger first-missing plan и complete-result/readiness contract.
|
||||
Новый endpoint или общий refresh coordinator не нужен.
|
||||
- **Guarantees at risk:** current revision/account membership, exact interval,
|
||||
cursor prevalidation, coverage полнота, atomic reconciliation и сохранённые
|
||||
суммы. Нельзя выдавать отсутствие истории за нулевой income.
|
||||
- **Counterarguments / trade-offs:** прежний UX preloads всю историю и облегчает
|
||||
comparisons. Current specs это запрещает; cold history Income может оставаться
|
||||
incomplete до разрешённой загрузки Ledger. Этот UX нужно согласовать явно,
|
||||
не восстанавливать covert import. Не удалять loading/history metadata вслепую.
|
||||
- **Verification or rollback:** isolated seeded real SQLite + mocked T-Bank:
|
||||
выбранный полный January и отсутствующий December; Ledger GET/no observe/observe
|
||||
должен дать0 RPC; missing January должен получить толькоJanuary; Income GET
|
||||
должен не создавать membership/work. Assert requested intervals, not only count.
|
||||
Existing tests `backend/test/tbank-ledger-local.e2e-spec.ts:368–409` фактически
|
||||
допускают RPC у observe; income helper `tbank-income-local.e2e-spec.ts:33–43`
|
||||
по умолчанию тестирует observe. Regression proof нужно поменять, не считать
|
||||
эти тесты доказательством ordinary GET. Rollback targeted owner diff при нарушении
|
||||
completeness, без удаления сохранённой БД.
|
||||
- **Related finding IDs:** FLOW-002, FLOW-008, FLOW-010.
|
||||
|
||||
### FLOW-002 — Screen timer вручную обходит enabled и readiness
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `frontend/src/features/tbank-connection/model/screen-sync.ts:7–35,39–51`;
|
||||
`frontend/src/shared/api/authenticated-query.ts:45–50`;
|
||||
`frontend/src/entities/bond/api/queries.ts:45–104`;
|
||||
`frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–13`;
|
||||
`frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–55`.
|
||||
- **Claim / consumers:** пустой/короткий search и detail-dependent resources
|
||||
остаются в resources array даже когда enabled=false; 30-minute interval всё
|
||||
равно вызывает refetch. Возможна попытка без session или UID. Восемь production
|
||||
page callers найдено в account/accounts/operations/income/events/bond/bonds/stock.
|
||||
- **Current mechanism:** interval управляется lifetime page mount, а не Query
|
||||
key/enable state; ScreenResource даже не требует isEnabled. Refresh/retry
|
||||
тоже не фильтруют disabled. TanStack ручной refetch исполняется с enabled=false.
|
||||
- **Impact / actual cost:** BondsPage idle: intended0 → один logical GET на tick;
|
||||
backend MinLength2 отклоняет его400 без RPC. BondPage без resolved UID на tick:
|
||||
до6 dependent logical GET attempts вместо0; invalid UUID paths отклоняются до
|
||||
source, но operations пустой account identifier формирует неправильный URL.
|
||||
Число successful HTTP attempts зависит от session/abort. **Impact unmeasured.**
|
||||
- **Why now:** disabled is explicit prerequisite, не спорная freshness policy.
|
||||
- **Minimum change:** учитывать isEnabled и не отменять уже выполняющийся read
|
||||
на timer path; либо native Query refetchInterval у owner. Первый минимальный
|
||||
guard достаточен для дефекта; полная policy migration зависит от FLOW-008.
|
||||
- **Guarantees at risk:** auth gating, dependent-query ordering, cancellation,
|
||||
HTTP-only retry и connection reset; не добавлять skip guards к каждому page.
|
||||
- **Counterarguments / trade-offs:** timer появляется лишь через30мин; backend
|
||||
validation снижает impact. Native interval может перестать polling background
|
||||
tab, что надо решить явно. Imperative user retry доступных ресурсов сохранить.
|
||||
- **Verification or rollback:** fake time+MSW: короткий q, unresolved UID, signed-out
|
||||
session →0 GET после tick; затем enabled→один GET для same key; timer during
|
||||
active read не запускает replacement. Existing
|
||||
`screen-sync.test.tsx:21–80` проверяет manual refresh, не disabled timer.
|
||||
Использовать App policy, а не retry-disabled test-client как её замену.
|
||||
- **Related finding IDs:** FLOW-001, FLOW-008, FLOW-010.
|
||||
|
||||
### FLOW-003 — Prices ждёт все details без data dependency
|
||||
|
||||
- **Decision:** SIMPLIFY. **Severity:** P2. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:295–325`;
|
||||
`catalog-selection.ts:5–40`;
|
||||
`backend/src/integrations/tbank/tbank.client.ts:358–379,441–447`;
|
||||
`frontend/src/pages/bonds/ui/BondsPage/BondsPage.view.tsx:27–109`;
|
||||
`backend/src/modules/investments/instruments/bonds/application/bond-catalog.service.ts:25–55,92–118`.
|
||||
- **Claim / consumers:** текущая compact bond card требует nominal/maturity,
|
||||
frequency/floating/perpetual и logo/exchange; цены требуют только selected UID,
|
||||
уже известный сразу после FindInstrument. Зависимость prices→all details лишняя.
|
||||
- **Current mechanism:** search → selected≤30 → await Promise.all(N BondBy) →
|
||||
await batchGetLastPrices(N UID) → DTO. Сами details уже parallel.
|
||||
- **Impact / actual cost:** total=`N+2` остаётся; critical path сейчас
|
||||
`T_search + max(T_detail_i) + T_prices`, включая очереди/retries. Можно сделать
|
||||
`T_search + max(max(T_detail_i),T_prices)` только при доступном limiter budget.
|
||||
**Impact unmeasured**, milliseconds неизвестны.
|
||||
- **Why now:** это независимая зависимость доказана аргументами API, без изменения
|
||||
полноты карточки/контракта и без нового endpoint.
|
||||
- **Minimum change:** после selection начать batch prices вместе с detail cohort
|
||||
через existing promises/Promise.all и existing TbankClient limiter.
|
||||
- **Guarantees at risk:** same selected UID/prices join, selection/order/ISIN
|
||||
dedup, bounded result count, valid token handling, отсутствие partial success
|
||||
вместо принятой whole-request error.
|
||||
- **Counterarguments / trade-offs:** если detail fails, ранее prices не расходовался,
|
||||
после parallel может добавиться1 logical RPC. Queue contention может съесть
|
||||
latency benefit. Сократить N до0 нельзя: InstrumentShort proto `1398–1416` не
|
||||
содержит используемые bond conditions, тогда как Bond `513–516,535` содержит.
|
||||
Bulk full catalog другой trade-off и не внедряется без provider/UX proof.
|
||||
- **Verification or rollback:** controlled mock detail deferred, prices must start
|
||||
before release; assert exact same DTO/count=`N+2`, empty search=1; detail failure
|
||||
не возвращает partial success. Synthetic limiter-aware timing отдельно. Если
|
||||
время не улучшается вне noise или failure-rate cost растёт — вернуть порядок.
|
||||
- **Related finding IDs:** FLOW-004, FLOW-008, FLOW-010.
|
||||
|
||||
### FLOW-004 — Permanent upstream auth failure ошибочно усиливается GET retry
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `backend/src/integrations/tbank/tbank.client.ts:527–565`;
|
||||
`backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:149–165,227–249,295–317`;
|
||||
`catalog-enrichment.service.ts:241–267`;
|
||||
`backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:65–68`;
|
||||
`backend/src/common/filters/http-exception.filter.ts:19–31,50–64`;
|
||||
`frontend/src/shared/api/client.ts:127–193`.
|
||||
- **Claim / consumers:** online search/detail/ticker/company не используют
|
||||
существующий translator. Remote PERMISSION_DENIED/UNAUTHENTICATED после
|
||||
onlineToken локальной проверки даёт Error →500 InternalServerError → GET retries.
|
||||
Screener POST тоже теряет public code, но transient transport не повторяет POST.
|
||||
- **Current mechanism:** invalidToken=true в local row корректно даёт400 до RPC;
|
||||
finding относится к токену, ещё не известному backend как invalid. gRPC auth
|
||||
не retries, но application mapping пропущен. Durable enrichment catch — другой
|
||||
путь с rejectMembershipToken, он не доказывает защиту online path.
|
||||
- **Impact / actual cost:** Search auth fail на первом FindInstrument:
|
||||
один queryFn делает3 HTTP/3 upstream RPC вместо1/1. Failure в detail cohort:
|
||||
до3(1+N) logical RPC, цены не вызываются. Consumer получает InternalServerError
|
||||
вместо ожидаемого публичного InvalidTbankToken для recovery.
|
||||
**Impact unmeasured.**
|
||||
- **Why now:** permanent/transient codes уже разделены frontend и integration;
|
||||
ложный500 превращает штатную смену credentials в повторную работу.
|
||||
- **Minimum change:** reuse
|
||||
`backend/src/integrations/tbank/tbank-token.service.ts:26–35` на online boundary,
|
||||
сохраняя pending durable attempt policy и safe error envelope.
|
||||
- **Guarantees at risk:** без raw credential/error disclosure; не маркировать новую
|
||||
connection invalid по late response старого token; если нужно persistence
|
||||
invalidation, только revision-aware CAS. Для прекращения retries достаточно
|
||||
translation, не нужно нового token state machine.
|
||||
- **Counterarguments / trade-offs:** auth error может означать недостаточные scope,
|
||||
но existing TbankClient уже сводит её к InvalidToken. Offline known invalid
|
||||
connection уже защищена; проблема только нового upstream refusal. При transient
|
||||
unavailable retry сохраняется.
|
||||
- **Verification or rollback:** configured Nest HTTP с mocked RPC auth status →
|
||||
400/INVALID_TBANK_TOKEN; MSW через actual client →1 GET, no transient delays.
|
||||
Separate Search failure after N queued details, company и POST screener.
|
||||
Existing `tbank.client.spec.ts:230–241` проверяет RPC, а не online HTTP mapping.
|
||||
- **Related finding IDs:** FLOW-003, FLOW-010.
|
||||
|
||||
### FLOW-005 — Bank history N+1 внутри embedded read transaction
|
||||
|
||||
- **Decision:** SIMPLIFY. **Severity:** P2. **Confidence:** High. **Effort:** M.
|
||||
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:41–91,122–165,333–369`;
|
||||
`backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:62–72`;
|
||||
`frontend/src/entities/bank-card/api/queries.ts:120–144`;
|
||||
`frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:35–45`.
|
||||
- **Claim / consumers:** list and refund-expense picker используют mapper-loader
|
||||
одиночной операции; одинаковая карта/category/refund family перечитывается для
|
||||
каждой строки, хотя page уже загружена.
|
||||
- **Current mechanism:** base ownership+count+page=3 reads;
|
||||
row =card1 +target(t) +source(s) +category(c) +refunds(r).
|
||||
Для корректных видов: income1, transfer2, expense3, refund4. Limit≤100.
|
||||
Default UI10 expenses →33 reads; page100 refunds →403 reads. Это условные
|
||||
examples, не статистика production. Corrupt mixed shape может иметь5 row reads.
|
||||
- **Impact / actual cost:** per-page cost линейна числу rows; refund sums читают
|
||||
полные sibling lists многократно. `all` frontend складывает эту работу по HTTP
|
||||
pages. SQLite embedded: **нет** remote DB RTT; materialization/queries остаются.
|
||||
Promise.all вокруг rows не устраняет model call count. **Impact unmeasured.**
|
||||
- **Why now:** count структура доказана; list hot path и all-mode существуют.
|
||||
Runtime ускорение надо доказать перед включением более сложного loader.
|
||||
- **Minimum change:** сохранить transaction; загрузить distinct cards, source
|
||||
expenses, categories, refund rows для page одним bounded набором запросов,
|
||||
затем применить существующую projection. При page empty не грузить relations.
|
||||
Не менять single-operation/write path ради list optimization.
|
||||
- **Guarantees at risk:** owner in every batch query, exact bigint sums,
|
||||
archived/deleted/reference semantics; refund row availableRefund исключает
|
||||
именно текущую row, expense — нет. Нельзя удалять financial transaction/CAS/
|
||||
idempotency. `bank-operations.service.ts:181–195` содержит write invariant,
|
||||
а здесь меняется только read projection.
|
||||
- **Counterarguments / trade-offs:** default page10 и SQLite могут быть достаточно
|
||||
быстры; отдельный page loader увеличит maintenance. Existing bounded batch
|
||||
ориентир3+≤4 relation reads, а не обещание одного SQL. Встроенный include должен
|
||||
проверяться на фактический SQL/bytes; full unbounded refunds family может всё
|
||||
ещё доминировать.
|
||||
- **Verification or rollback:** synthetic page mixed income/transfer/expense/
|
||||
несколько refunds одной expense, cross-user/deleted cases; DTO равен before,
|
||||
count не растёт на1 loader на row. Затем query/duration trace на isolated SQLite.
|
||||
При нейтральной latency и неоправданной сложности — сохранить current loader.
|
||||
Existing `backend/test/bank-cards.e2e-spec.ts:424–462,550–580,779–791` сохраняет
|
||||
observable semantics; execution в этом аудите отсутствует.
|
||||
- **Related finding IDs:** FLOW-009, FLOW-010.
|
||||
|
||||
### FLOW-006 — Hourly calendar refresh не обновляет сохранённые calendars
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–175`;
|
||||
`backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182–239`;
|
||||
`backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,189–193,596–625,679–692`;
|
||||
`specs/openspec/specs/hourly-investment-sync/spec.md:8–9`.
|
||||
- **Claim / consumers:** hourly должен обновлять события текущих holdings, но
|
||||
requestMany получает force=false; stable успешный calendar не обновляется.
|
||||
Bond-events не имеет rangeKey, поэтому suppress сохраняется после смены дня;
|
||||
dividend key changes daily, но внутри дня calendar reuses previous window.
|
||||
- **Current mechanism:** только quote policy проверяет TTL; dividends/bond-events
|
||||
stable. New positions/calendars действительно грузятся, а сохранённые нет.
|
||||
Forecast может выглядеть полным с прежними событиями при новом Portfolio.
|
||||
- **Impact / actual cost:** warm calendar upstream budget0 не является
|
||||
корректной экономией, если provider изменил объявленные/отменённые выплаты.
|
||||
Fix добавляет calls, не уменьшает их. При hourly refresh effective unique
|
||||
S ранее сохранённых stock windows adds до S GetDividends; B ранее сохранённых
|
||||
bond calendars adds Σ(c_b+o_b+1) MOEX HTTP при полном refresh, с учётом shared
|
||||
producers/cache scope. Уже отсутствующие источники и сейчас загружаются.
|
||||
**Impact unmeasured**, изменённые real calendars не измерялись.
|
||||
- **Why now:** требование hourly explicitly covers future events; permanent stable
|
||||
reuse не удовлетворяет его даже без выбора точного TTL общего online справочника.
|
||||
- **Minimum change:** hourly caller принудительно обновляет именно calendar kinds
|
||||
или equivalent existing policy; stable detail metadata reuse сохраняется.
|
||||
Обновление нужного календаря не должно включать quotes/full market history.
|
||||
- **Guarantees at risk:** shared producer isolation, complete multi-page MOEX,
|
||||
same portfolio quantities, source version checks и fallback при error. Force
|
||||
whole source group вслепую может ненужно повторять detail каждого часа.
|
||||
- **Counterarguments / trade-offs:** calendars меняются редко, но не неизменяемы;
|
||||
обязательный hourly refresh увеличивает rate budget. Нужно измерить масштабы и
|
||||
coalescing одного UID между счетами; осторожно повторно не запускать same source
|
||||
после уже завершённого первого account refresh в этом же walk.
|
||||
- **Verification or rollback:** two successful controlled hourly source passes
|
||||
same window, второй upstream calendar изменён/отменён → новое saved version
|
||||
и forecast; second pass failure сохраняет first complete timestamps. Проверить
|
||||
2 accounts same UID, no duplicate calendar call внутри одного намеренного pass.
|
||||
Existing future-events suite запускает manual source refresh, scheduler stub
|
||||
(`backend/test/upcoming-events.e2e-spec.ts:140–147`), не полный hourly tick.
|
||||
- **Related finding IDs:** FLOW-007, FLOW-010.
|
||||
|
||||
### FLOW-007 — Forecast fallback блокирует новый complete candidate
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M.
|
||||
- **Evidence:** `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173,271–275`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–80,99–174`;
|
||||
`backend/src/modules/investments/portfolio/application/forecast.service.ts:35–65,77–93,98–120`;
|
||||
`specs/openspec/specs/brokerage-account-upcoming-events/spec.md:9–20,63–76`.
|
||||
- **Claim / consumers:** next-day hourly get creates new window scope; no exact
|
||||
previous row + overlapping old forecast → early return old fallback even with
|
||||
newly computed complete candidate. `findMany` fallback has no recency order.
|
||||
- **Current mechanism:** resolve conflates saved result identity and requested
|
||||
filter. It computes candidate in Events GET, takes write fence, then returns
|
||||
first overlap before validating/publishing new source revisions. No overlap
|
||||
with old forecast and candidate unavailable instead returns null, although
|
||||
intended semantics specifies successful empty filtered saved forecast.
|
||||
- **Impact / actual cost:** upstream/calendar and repeated local calculation могут
|
||||
не менять visible result; new candidate publication0 despite completed work.
|
||||
Age/value wrongness is source-inferred conditional, runtime **UNVERIFIED**;
|
||||
**Impact unmeasured**. Не просто лишний loop: repeated work cannot reach UI.
|
||||
- **Why now:** hourly rolling windows сдвигаются ежедневно; consumer требует
|
||||
исходный saved window/calculatedAt и фильтрацию, а не создание identities по GET.
|
||||
- **Minimum change:** в existing forecast owner отделить selection последнего
|
||||
complete result от HTTP window filter; candidate publication не блокировать
|
||||
только из-за другого window key. Read-only selection должен работать и для
|
||||
непересекающегося запроса. Contract migration scope согласовать перед реализацией.
|
||||
- **Guarantees at risk:** dependency/source version CAS `80–145`, membership and
|
||||
connection generation fence, complete-only publish, exact financial totals,
|
||||
unchanged calculatedAt при read filter, old complete при failed update.
|
||||
- **Counterarguments / trade-offs:** узкий GET не должен пересчитывать successful
|
||||
saved forecast или менять timestamp — existing fallback защищает это. Простое
|
||||
удаление early return сломает такой UX; различить producer update и consumer
|
||||
selection, а не ослабить проверки. «Latest» требует deterministic order.
|
||||
- **Verification or rollback:** save complete W0, update quantities/calendar,
|
||||
hourly produce complete W1 overlapping W0 → W1 доступен; narrow/non-overlap GET
|
||||
сохраняет исходные time/window и filters; failed W1 сохраняет W0; dependency race
|
||||
rejects publication. Existing `upcoming-events.e2e-spec.ts:619–641` защищает narrow
|
||||
filter, его сохранить; добавить distinct producer W0→W1 outcome в isolated DB.
|
||||
- **Related finding IDs:** FLOW-006, FLOW-009, FLOW-010.
|
||||
|
||||
### FLOW-008 — Общая freshness policy требует явного решения
|
||||
|
||||
- **Decision:** INVESTIGATE. **Severity:** P3. **Confidence:** Medium. **Effort:** S.
|
||||
- **Evidence:** `frontend/src/app/ui/App.tsx:13–19`;
|
||||
`frontend/src/entities/portfolio/api/usePortfolio.ts:3–5`;
|
||||
`frontend/src/entities/brokerage-account/api/useBrokerageAccounts.ts:7–17`;
|
||||
`frontend/src/features/tbank-connection/model/screen-sync.ts:20–35`;
|
||||
`docs/adr/019-hourly-reads-and-online-instrument-reference.md:27–29`;
|
||||
`frontend/.ai/conventions/data.md:70–74`.
|
||||
- **Claim / consumers:** source включает focus/reconnect; convention говорит focus
|
||||
off; ADR «не чаще30мин». StaleTime30s допускает GET после mount/focus/reconnect
|
||||
через30s, одновременно с interval30min. Факт policy divergence доказан, но
|
||||
трактовка initial loading/retry/manual actions и freshness UX не утверждена.
|
||||
- **Current mechanism:** Query defaults+entity freshness+page-native timer;
|
||||
staleTime не служит timer или строгим throttle; interval не зависит от staleTime.
|
||||
- **Impact / actual cost:** C и detail могут повторять online RPC; B/D/E повторяют
|
||||
local work и, пока FLOW-001 не исправлен, scheduling. Request count зависит от
|
||||
пользователя/visibility; не фиксирован. **Impact unmeasured.**
|
||||
- **Why now:** выбирать caching наугад может ухудшить cold completeness на30мин.
|
||||
- **Minimum change:** сначала уточнить правило; затем при подтверждении перенести
|
||||
периодические GET к existing Query owner, убрать obsolete page polling и `_scope`.
|
||||
Не вводить общий refresh registry. `ponytail-audit native:` уже установленный
|
||||
Query interval вместо собственного timer, если его visibility UX подходит.
|
||||
- **Guarantees at risk:** initial readiness, cached snapshot, connection reset,
|
||||
user retry, backend hourly freshness distinct from client response freshness.
|
||||
- **Counterarguments / trade-offs:** более частые local reads ускоряют появление
|
||||
первого snapshot; online instrument spec требует online-at-request, а не
|
||||
necessarily durable cache. Background-tab interval и reconnect UX требуют выбора.
|
||||
- **Verification or rollback:** fake-time App policy test: mount/focus/reconnect/
|
||||
timer/manual retry до/после chosen budget; evaluate first-null→complete latency
|
||||
на synthetic sources. Не count test wrapper defaults как production policy.
|
||||
- **Related finding IDs:** FLOW-001, FLOW-002, FLOW-003.
|
||||
|
||||
### FLOW-009 — Full snapshot/analytics DTO cost доказан структурно, benefit не установлен
|
||||
|
||||
- **Decision:** INVESTIGATE. **Severity:** P3. **Confidence:** Medium. **Effort:** M.
|
||||
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:25–47`;
|
||||
`backend/src/modules/investments/portfolio/application/portfolio.service.ts:57–59,79–95`;
|
||||
`brokerage-accounts/brokerage-accounts.service.ts:17–39`;
|
||||
`upcoming-events/upcoming-events.service.ts:60–71,206–239`;
|
||||
`income/income-analytics.service.ts:138–162,233–284`;
|
||||
`frontend/src/pages/account-income/model/display.ts:97–129`.
|
||||
- **Claim / consumers:** single-account read loads all account snapshot JSON before
|
||||
service filter; Accounts summary repeats full snapshot hydration and hourly read;
|
||||
Events rereads Portfolio/metadata. Income delivers derived buckets/instruments
|
||||
unused by current UI. Это реальные opportunities, не подтверждённый slow endpoint.
|
||||
- **Current mechanism:** reuse whole saved dataset for independent HTTP resources;
|
||||
forecast GET validates/publishes under CAS; income period entries intentionally
|
||||
unbounded. Нет runtime hydrate/dehydrate registry frontend.
|
||||
- **Impact / actual cost:** logical repeated reads/JSON work; bytes/CPU/SQLite time
|
||||
unknown. Removal source calls could change race consistency; **Impact unmeasured**.
|
||||
- **Why now:** пригодно для дешёвого isolated trace перед новым summary endpoint/cache.
|
||||
- **Minimum change:** измерить per-route bytes/model SQL/JSON time; кандидат —
|
||||
передавать account predicate existing repository (single-account read), reuse
|
||||
`saved.hourly` при Accounts list, сократить unused DTO только с contract proof.
|
||||
No change допустим при дешёвых local reads. Не добавлять BFF endpoint вслепую.
|
||||
- **Guarantees at risk:** user/revision/account scope, consistent dependency
|
||||
projection, last complete forecast/timestamps, exact unlimited-period entries,
|
||||
stable public artifacts и внешние consumers.
|
||||
- **Counterarguments / trade-offs:** общий dataset упрощает all aggregation;
|
||||
independent GETs дают section error isolation. Spec дохода требует все entries
|
||||
сразу (`spec.md:157–179`) и unlimited duration (`10`), поэтому pagination требует
|
||||
product/contract decision. «Unused UI field» не доказывает no external consumers.
|
||||
- **Verification or rollback:** isolated SQLite one account vs many, same data/DTO,
|
||||
error/isolation race; compare bytes and hydration with query trace. Проверить
|
||||
всех контрактных consumers до изменения generated snapshot. Если gain neutral,
|
||||
оставить существующий путь.
|
||||
- **Related finding IDs:** FLOW-005, FLOW-007, FLOW-010.
|
||||
|
||||
### FLOW-010 — Защищающая сложность и достаточное reuse: KEEP
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P2. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `backend/src/integrations/tbank/tbank-access.service.ts:158–169,262–288`;
|
||||
`backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–133`;
|
||||
`operations/infrastructure/brokerage-operations.repository.ts:128–144,602–698,703–881`;
|
||||
`portfolio/infrastructure/forecast.repository.ts:35–46,80–145`;
|
||||
`instruments/stocks/application/stock-screener.service.ts:19–26,84–109`;
|
||||
`frontend/src/features/tbank-connection/model/connection-boundary.ts:24–39`;
|
||||
`frontend/src/features/auth/model/auth.vm.ts:73–90`.
|
||||
- **Claim / consumers:** финансовые UI, pagination, forecasts и соединения уже
|
||||
используют meaningful completeness/access/CAS boundaries и cache reuse.
|
||||
Совпадение keys/SHA/interfaces или цена некоторых reads не оправдывает удаление.
|
||||
- **Current mechanism:** private user/revision dataset; conditional work claims;
|
||||
staged complete operation publish; signed cursor filter/version binding;
|
||||
shared public source coalescing; Query cancel/reset on connection and clear on
|
||||
logout; bounded upstream queue/deadline; screener snapshot stable pages.
|
||||
- **Impact / actual cost:** prevents cross-connection publication, wrong money,
|
||||
duplicate/replayed writes и pagination inconsistency. **Impact unmeasured**,
|
||||
это protected guarantee, не confirmed performance defect.
|
||||
- **Why now:** любой optimization выше должен сохранить эту equivalence boundary.
|
||||
- **Minimum change:** no change to safeguards. Retain search details needed by
|
||||
actual card, batch prices, SQLite batches400/100 и correct DTO whitelist.
|
||||
- **Guarantees at risk:** authorization/privacy, secret handling, exact currency/
|
||||
decimal strings/bigint, rollback/idempotency/CAS, complete-only result,
|
||||
controlled scheduler lifecycle, public contract stability.
|
||||
- **Counterarguments / trade-offs:** SQL/JSON validation имеет local cost; alternatives
|
||||
допустимы только с equal guarantees. Не заменять signed cursor offset у Ledger,
|
||||
не хранить user portfolios в generic shared instrument cache.
|
||||
- **Verification or rollback:** retain isolated cross-user/revision/late publication,
|
||||
cursor, complete-empty/failed-refresh, banking refund/replay/atomicity tests.
|
||||
Source inspection не доказывает весь real concurrency/security набор; это
|
||||
необходимый regression plan, не заявление «security audit пройден».
|
||||
- **Related finding IDs:** FLOW-001, FLOW-002, FLOW-003, FLOW-004, FLOW-005, FLOW-006, FLOW-007, FLOW-009.
|
||||
|
||||
### Before/after critical-path outline — две главные проблемы
|
||||
|
||||
**1. FLOW-001, ownership/import.**
|
||||
|
||||
```text
|
||||
BEFORE ordinary income, membership fresh/full coverage:
|
||||
access → readPage → membership reservation(write fence + read)
|
||||
→ schedule(access → readPage → readPlan(access → coverage)) → readPage
|
||||
response local; detached import = m + Σp(R_T ∪ R_H), if missing
|
||||
|
||||
AFTER intended passive income:
|
||||
access → readPage → exact totals/comparisons/entries → response
|
||||
import from this read = 0; hourly and explicitly allowed missing Ledger remain
|
||||
|
||||
BEFORE Ledger first missing period:
|
||||
access → validate cursor if present → readPlan chosen + outside lifetime history
|
||||
→ local readPage → metadata batch → response
|
||||
worker queue = pages(chosen gaps) + pages(outside gaps)
|
||||
|
||||
AFTER Ledger:
|
||||
same access/cursor/coverage/DTO protection, request only chosen gaps
|
||||
worker queue = pages(chosen gaps); no hidden membership/portfolio/calendar refresh
|
||||
```
|
||||
|
||||
Fully warm conditional income foreground model reads20→6; this is static count,
|
||||
not20/6 speed ratio. Worker total сокращается на `m+Σp(R_H)` плюс выбранные Income gaps, которые
|
||||
должны принадлежать разрешённому Ledger owner. Нельзя удалить permitted Ledger
|
||||
first-load или выдавать empty completeness за отсутствие операций.
|
||||
|
||||
**2. FLOW-003, search waterfall.**
|
||||
|
||||
```text
|
||||
BEFORE: Token → Search → [Detail1 || … || DetailN] → PricesBatch → DTO → UI
|
||||
AFTER: Token → Search → [[Detail1 || … || DetailN] || PricesBatch] → DTO → UI
|
||||
TOTAL: N+2 → N+2 (N>0); empty1 →1
|
||||
PATH: Tsearch + max(Tdetails) + Tprices
|
||||
→ Tsearch + max(max(Tdetails), Tprices), subject to limiter contention
|
||||
```
|
||||
|
||||
Do not count N concurrent details as N sequential latency stages. A full failed
|
||||
detail response remains failed; moving price start earlier can spend1 extra call
|
||||
on that error path. No latency gain claimed until limiter-aware synthetic proof.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE — summary decision table
|
||||
|
||||
| Decision | IDs | Минимальная стратегия | Решение сейчас |
|
||||
| --- | --- | --- | --- |
|
||||
| DELETE | — | obsolete outside-scope refresh branches удаляются внутри FLOW-001, отдельного blanket deletion нет | не удалять весь planInvestmentScreen/status/coverage |
|
||||
| FIX | FLOW-001/002/004 | один source owner; readiness guard; existing token translator | confirmed actionable |
|
||||
| FIX | FLOW-006/007 | current calendars + producer/consumer forecast distinction | confirmed semantics, regression proof before implementation |
|
||||
| SIMPLIFY | FLOW-003 | parallel independent prices with details | count unchanged; latency experiment with rollback |
|
||||
| SIMPLIFY | FLOW-005 | bounded page relation loading instead of row loaders | DB work reduction structure known; runtime benefit unmeasured |
|
||||
| KEEP | FLOW-010 | native Query sharing, complete snapshots, signed cursor, CAS, transaction, whitelist, limiter, screener snapshot | preserve invariants |
|
||||
| INVESTIGATE | FLOW-008 | explicit freshness/readiness policy; native Query interval if suitable | no assumed business decision |
|
||||
| INVESTIGATE | FLOW-009 | local hydration/DTO measurement before new cache/endpoint | no current latency finding |
|
||||
|
||||
Ponytail complexity-pass: `native:` Query-owned interval conditional on agreed
|
||||
policy; `shrink:` убрать независимую последовательность prices; `delete:` убрать
|
||||
неразрешённые history refresh branches, сохранив source owner и protected guards.
|
||||
**net: lines not measured; 0 dependency removals established.** Число строк не
|
||||
выдумывается без prepared diff; новые dependency/registry не предлагаются.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| Strategy / IDs | No change | Expected cost/benefit class | Freshness / UX / rate / maintenance trade-off | Measurement |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ownership FLOW-001 | unrelated history continues on reads | runtime RPC, less source queue work, simpler refresh ownership | cold income completeness remains explicit; chosen Ledger fetch stays; no covert full backfill | not measured; request-set delta proven |
|
||||
| readiness FLOW-002 | disabled timer requests retained | runtime HTTP + less invalid failures; one shared guard | preserve enabled transitions/session reset/manual retry; avoid cancel/restart storms | not measured; idle tick delta1 or dependent6 inferred |
|
||||
| parallelism FLOW-003 | current detail→prices order, fewer price calls on detail fail | critical-path latency, small local code change | no total-call reduction, ≤1 extra price on failure; existing limiter still applies | not measured |
|
||||
| token mapping FLOW-004 | permanent auth500 continues | fewer useless HTTP/RPC retries, correct recovery | safe public code; don't invalidate new generation using old refusal | not measured; first-RPC scenario3→1 |
|
||||
| batching FLOW-005 | default10 may already be fast | DB/model reads/materialization; some maintenance increase | same transaction/user/refund totals; avoid row N+1, no remote DB claim | not measured |
|
||||
| calendar freshness FLOW-006 | stable cache saves calls indefinitely | correctness/currentness, MORE upstream calls | hourly calendar-specific scope, multi-account dedup, no full history/quote force | not measured; added source budget explicit |
|
||||
| forecast FLOW-007 | old overlap can suppress candidate | reliable visible update; avoid wasted upstream/calculation | producer/read filter distinct, full fallback/CAS preserved | not measured |
|
||||
| cache policy FLOW-008 | inconsistent timers/focus remains | possible HTTP/RPC and cognitive savings | 30min can delay initial complete state; online reference freshness distinct | not measured; INVESTIGATE |
|
||||
| DTO/hydration FLOW-009 | independent local reads retain error isolation | possible CPU/bytes/agent navigation reduction | unlimited exact income entries required; contract consumers must agree | not measured; INVESTIGATE |
|
||||
|
||||
**CI minutes:** не измерялись и не являются целью07. **Agent tokens/cognitive load:**
|
||||
ожидаемое качественное упрощение ownership, без numeric savings. **Batching search:**
|
||||
GetLastPrices уже batch; удаление N BondBy без equivalent card fields недопустимо.
|
||||
**Cache search:** durable reference copy запрещена current online semantics;
|
||||
ephemeral coalescing/TTL требует product freshness и token/revision-safe key,
|
||||
сейчас не confirmed action. **Removing obsolete refresh:** FLOW-001 даёт
|
||||
доказуемый request-set cut; blanket удаление scheduler не эквивалентно.
|
||||
|
||||
## 7. Top actions — не более 10
|
||||
|
||||
### Quick wins, подтверждённые механизмом
|
||||
|
||||
| Priority | Action | Value / risk / effort |
|
||||
| --- | --- | --- |
|
||||
| 1 | FLOW-002: shared timer readiness guard, no restart active read | explicit invalid HTTP eliminated; small scope/S |
|
||||
| 2 | FLOW-004: existing online token translator + HTTP/MSW proof | removes permanent retry loop, correct recovery; S |
|
||||
| 3 | FLOW-003: overlap prices/detail with controlled-source check | local change/S; revert if limiter/error cost erases benefit |
|
||||
|
||||
### Требуют behavioral / DB proof перед выпуском
|
||||
|
||||
| Priority | Action | Value / risk / effort |
|
||||
| --- | --- | --- |
|
||||
| 4 | FLOW-001: remove outside-scope backfill + passive Income | strongest source-budget reduction; cold history UX/completeness proof/M |
|
||||
| 5 | FLOW-006: calendar-only hourly refresh with per-walk dedup evidence | current calendar guarantee; increases known upstream budget/M |
|
||||
| 6 | FLOW-007: distinguish forecast production/read filtering | removes old-candidate suppression; CAS/narrow-read regression/M |
|
||||
| 7 | FLOW-005: bounded bank list loader only after isolated cost check | per-row DB reads reduced; refund/owner contract and maintenance/M |
|
||||
|
||||
ADR нужен только если меняется принятая freshness/cold-history/forecast boundary,
|
||||
а не как обязательный документ для guard или Promise.all. FLOW-008/009 не входят
|
||||
в подтверждённые top fixes до ответов и измерений. No change — допустимый результат
|
||||
измерения для performance-only simplification.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **05 Frontend composition:** проверить consumer resource metadata на AccountPage boundary —
|
||||
`frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135`; проверить
|
||||
передачу в container/view. Presentation расследование здесь не выполнялось.
|
||||
- **06 Backend domain style:** отдельные похожие `publicInstrument` projections —
|
||||
`catalog-discovery.service.ts:499–530`, `catalog-enrichment.service.ts:646–677`;
|
||||
допустимость объединения owner проверить отдельно, whitelist KEEP.
|
||||
- **04 Dead code:** production `TbankSyncService.request/dispatch` не найден среди
|
||||
callers; controller только status — `tbank-sync.controller.ts:31–41`,
|
||||
`tbank-sync.service.ts:54–78`. Удаление недоступной orchestration — другой audit,
|
||||
runtime request budget текущих flows от её удаления не меняется.
|
||||
- **13 Reliability:** scheduler catch/settle/poll и hourly eligibility —
|
||||
`tbank-hourly.scheduler.ts:108–110,178–218`, `tbank-sync.repository.ts:189–201`;
|
||||
stalled work/пропущенный срок меняют допустимость следующей работы. Без отдельного
|
||||
security/reliability расследования в07, finite max walk duration unknown.
|
||||
- **08 Tests:** Ledger observe assertions допускают scheduling, income helper по
|
||||
умолчанию observe, scheduler mocked — source refs в FLOW-001/006. Нужен outcome
|
||||
proof actual ordinary callers, не performance suite вместо regression.
|
||||
- **15 Specs/docs:** старый Ledger cache/upstream requirement
|
||||
`brokerage-account-ledger/spec.md:97–110` конфликтует с local/hourly
|
||||
`131–140`; online-reference addendum docs/bonds `147–151` сосуществует со старым
|
||||
refresh explanation `119–123`. Не восстанавливать obsolete cache автоматически.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
### Вопросы, действительно меняющие решение
|
||||
|
||||
1. **Cold historical Income после пассивного GET:** пользователь сначала открывает
|
||||
Ledger, либо требуется иной явный permitted owner? Это меняет UX FLOW-001,
|
||||
но не делает текущий hidden membership/backfill разрешённым.
|
||||
2. **30-minute client rule:** применяется к periodic revalidation только или также
|
||||
focus/mount/reconnect; initial incomplete и user recovery exceptions? Самая
|
||||
дешёвая проверка — owner decision + fake-time App policy test, FLOW-008.
|
||||
3. **Calendar budget масштаба:** A, unique held UID, MOEX page counts и one walk
|
||||
across accounts? Controlled fixtures с repeated UID перед calendar force;
|
||||
actual production distribution не необходима для correct outcome proof.
|
||||
4. **Forecast producer identity:** один latest complete snapshot на account либо
|
||||
поддерживаемые producer windows? Нужен deterministic selection и producer/read
|
||||
distinction до FLOW-007, без ослабления source CAS.
|
||||
5. **Bank/DTO масштаб и внешние consumers:** действительно ли local reads/bytes
|
||||
важны, и кто использует income buckets/instruments/full detail fields? Ответ
|
||||
меняет FLOW-005 runtime adoption и FLOW-009 contract cut, не надо нового endpoint
|
||||
ради потенциального gain.
|
||||
|
||||
### Non-production verification
|
||||
|
||||
| Finding | Самая дешёвая надёжная boundary / expected proof |
|
||||
| --- | --- |
|
||||
| FLOW-001 | existing isolated SQLite Ledger/Income suite + mocked broker; selected complete, outside missing, observe/no observe; interval set and zero forbidden work |
|
||||
| FLOW-002 | existing Vitest/MSW screen-sync test, fake time + real App query options; disabled/session/UID prerequisites; no timer cancel/restart |
|
||||
| FLOW-003 | controlled service mock deferred details/prices; unchanged DTO/count; optional limiter-aware synthetic timing, no live credentials |
|
||||
| FLOW-004 | configured Nest HTTP error mapping + MSW authenticatedFetch retry test; permanent1 attempt, transient retries retained |
|
||||
| FLOW-005 | isolated banking suite, mixed/refund family/cross-user/deleted cases; SQL/model-call trace+timing before adopting batch |
|
||||
| FLOW-006 | controlled two-pass hourly with shared UID+changed calendar/failure; real SQLite version/timestamp fallback |
|
||||
| FLOW-007 | W0→W1 producer, narrow/non-overlap passive read, dependency race; real DB CAS outcome |
|
||||
| FLOW-008/009 | product policy/contract owners + synthetic bytes/read/hydration trace; no release recommendation from intuition |
|
||||
|
||||
Future verification commands must use local `nvm use --silent`; inspect script
|
||||
side effects first. Existing frontend npm test scripts build packages;
|
||||
SQLite runner migrates disposable DB; они **не исполнялись** в этом read-only
|
||||
аудите. Новые scripts/frameworks/fixtures tools для самого аудита не создавались.
|
||||
|
||||
### Итоговая проверка отчёта и read-only
|
||||
|
||||
- Полный prompt и полный отчёт перечитаны; source refs, диапазоны, consumers,
|
||||
count units/formulas и все required fields проверены статически.
|
||||
- Semantic/Boy Scout review: найденные возможности не превращены в blanket
|
||||
removal; public DTO/transaction/CAS/cursor guards сохранены в recommendations;
|
||||
нет новой application abstraction/dependency/process gate.
|
||||
- `git diff --no-index --check /dev/null "audit-results/07-api-and-data-flow-efficiency-report.md"`
|
||||
в root cwd: пустой diagnostic output, exit1 при сравнении нового файла с
|
||||
`/dev/null`; whitespace errors не выведены. Exit проверен через
|
||||
`check_exit=$?; typeset -p check_exit`: `typeset check_exit=1`.
|
||||
- `git diff --check` в root/frontend/backend/specs/docs: пустой output, exit0.
|
||||
- Итоговые `git rev-parse HEAD && git status --short` всех пяти repo: SHA как в
|
||||
таблице metadata, tracked/staged/unstaged изменений нет. Новый отчёт root
|
||||
существует как разрешённый ignored audit artifact; отсутствие его в status
|
||||
само по себе не проверка содержимого, поэтому выполнен no-index check.
|
||||
`git check-ignore -v` подтвердил root `.gitignore:2:/*`; ignore не менялся.
|
||||
- Ограничения: no current runtime measurement/tests, no graph, no fresh OpenAPI
|
||||
generation, no current production log/trace; version-specific Prisma SQL
|
||||
expansion unknown. Все latency/throughput выигрыши остаются **UNVERIFIED**.
|
||||
|
||||
## Машинная таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| FLOW-001 | FIX | P2 | High | backend | backend/src/modules/investments/operations/application/brokerage-operations.service.ts; backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts; backend/src/modules/investments/income/income-analytics.controller.ts | GET plans m+pages(selected gaps+outside history), violates source ownership | M | FLOW-002,FLOW-008,FLOW-010 |
|
||||
| FLOW-002 | FIX | P2 | High | frontend | frontend/src/features/tbank-connection/model/screen-sync.ts; frontend/src/entities/bond/api/queries.ts | Manual timer refetch bypasses enabled/session/UID prerequisites | S | FLOW-001,FLOW-008,FLOW-010 |
|
||||
| FLOW-003 | SIMPLIFY | P2 | High | backend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts | Search N+2 RPC retains unnecessary details-to-prices waterfall | S | FLOW-004,FLOW-008,FLOW-010 |
|
||||
| FLOW-004 | FIX | P2 | High | backend+frontend | backend/src/integrations/tbank/tbank.client.ts; backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; frontend/src/shared/api/client.ts | Permanent online broker auth becomes500 and up to3 repeated GET/RPC attempts | S | FLOW-003,FLOW-010 |
|
||||
| FLOW-005 | SIMPLIFY | P2 | High | backend | backend/src/modules/banking/bank-cards/application/bank-operations.service.ts | Bank history 3+sum(1+t+s+c+r) embedded model reads/page | M | FLOW-009,FLOW-010 |
|
||||
| FLOW-006 | FIX | P2 | High | backend | backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; backend/src/modules/investments/upcoming-events/upcoming-events.service.ts; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts | Hourly stable calendar reuse can suppress updated future events | M | FLOW-007,FLOW-010 |
|
||||
| FLOW-007 | FIX | P2 | High | backend | backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts; backend/src/modules/investments/portfolio/application/forecast.service.ts | Overlap fallback can suppress newly complete rolling-window candidate | M | FLOW-006,FLOW-009,FLOW-010 |
|
||||
| FLOW-008 | INVESTIGATE | P3 | Medium | frontend+docs | frontend/src/app/ui/App.tsx; frontend/src/features/tbank-connection/model/screen-sync.ts; docs/adr/019-hourly-reads-and-online-instrument-reference.md | Conflicting30s/30min/focus policy needs readiness and freshness decision | S | FLOW-001,FLOW-002,FLOW-003 |
|
||||
| FLOW-009 | INVESTIGATE | P3 | Medium | backend+frontend | backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts; backend/src/modules/investments/income/income-analytics.service.ts; frontend/src/pages/account-income/model/display.ts | Full snapshots/derived DTO work exists but runtime benefit and contract cut unverified | M | FLOW-005,FLOW-007,FLOW-010 |
|
||||
| FLOW-010 | KEEP | P2 | High | backend+frontend | backend/src/integrations/tbank/tbank-access.service.ts; backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts; backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts; frontend/src/features/tbank-connection/model/connection-boundary.ts | Preserve isolation/completeness/CAS/atomicity/typed projection and existing dedup | S | FLOW-001,FLOW-002,FLOW-003,FLOW-004,FLOW-005,FLOW-006,FLOW-007,FLOW-009 |
|
||||
|
||||
Все Files в машинной таблице — paths относительно workspace.
|
||||
Actionable=FIX+SIMPLIFY+DELETE; KEEP и INVESTIGATE исключены из severity totals.
|
||||
@@ -0,0 +1,430 @@
|
||||
# Аудит 08 — Test Quality & Effectiveness
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Задача:** проверить, какие тесты различают сохранённые бизнес-инварианты и реальные регрессии, а какие проверяют другой сценарий, слишком раннее состояние или устаревший контракт.
|
||||
- **Протокол:** `audit-prompts/08-test-quality-and-effectiveness.md`.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`; это родительская папка четырёх самостоятельных репозиториев. Единственный создаваемый artifact — этот отчёт вне них.
|
||||
|
||||
| Checkout | Абсолютный путь | Точный HEAD | Начальное состояние |
|
||||
| --- | --- | --- | --- |
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | `git status --short`: пусто |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | чисто |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | чисто |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | чисто |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | чисто |
|
||||
|
||||
**Инструкции и skills.** Прочитаны локальные `AGENTS.md`, engineering/testing conventions и необходимые data/auth/integration правила. Установленный `ponytail-audit` загружен и применён к стоимости и кандидатам удаления тестов. Его фактический scope — over-engineering; корректность assertions исследована по протоколу аудита 08, а не приписана этому skill. `graphify` загружен; существующего `graphify-out/graph.json` нет. Построение графа не выполнялось, поскольку оно создаёт дополнительные artifacts и противоречит read-only контракту. Subagents не использовались.
|
||||
|
||||
**Предыдущие сведения.** `audit-results/technical-audit-2026-10-11.md` прочитан как реестр гипотез. Его ссылки на race tests, Portfolio и MOEX перепроверены по текущему вызовному пути. Совпадение HEAD с предыдущим аудитом не использовано как доказательство.
|
||||
|
||||
**Выполнено:** статическое чтение, поиски имён/consumers/assertions, `git rev-parse HEAD`, `git status --short`, одноразовый inventory через `rg --files`, проверка отчёта и diff. Дополнительно выполнен один целевой запуск двух чистых денежных suites:
|
||||
|
||||
```sh
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use --silent
|
||||
npm test -- --runInBand --no-cache --coverage=false --runTestsByPath src/integrations/tbank/decimal.spec.ts src/modules/banking/bank-cards/domain/rub-money.spec.ts
|
||||
```
|
||||
|
||||
Working directory: `backend`. **Результат: 2 suites PASS, 28 tests PASS, 0 snapshots; Jest сообщил 0.342 s.** Это время одного локального запуска, не измерение CI или общей эффективности. npm выдал предупреждение о пользовательской настройке `//registry`; выполнение завершилось успешно. Эти suites импортируют денежные функции, не открывают DB и не обращаются к upstream.
|
||||
|
||||
**Не выполнялось:** install, codegen, migrations, build, full-suite, verify, SQLite/HTTP suites, browser suites, coverage collection, mutation/removal исходников, mutation framework, обращения к брокерским/банковским API, commit/PR. Runtime-прохождение остальных тестов и предполагаемые исходы мутантов обозначаются отдельно. Исходники, тесты, конфигурации и четыре checkout не изменены.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт: сильная тестовая основа существует.** Денежные cases используют независимо заданные строки и границы `>2^53`/signed64; auth concurrency проходит реальный SQLite CAS; operations rollback выполняет часть записи до отказа следующего интервала. Их нельзя заменять одними mocks или удалять как «дублирующие coverage».
|
||||
2. **Главный риск: новый владелец обновления почти не проверен как выполняемый flow.** В inventory не найдены прямые проверки `tick`, `reserveHourly`, `settleHourly`; три integration suites явно заменяют scheduler. Проверки готового `meta` не доказывают запуск, сроки, завершение и результат реального обхода. Это единственный P1 finding о пробеле защиты.
|
||||
3. **Факт: backend late-portfolio test не создаёт заявленную гонку.** Deferred установлен на RPC, но stimulus — локальный GET; PUT тоже не запускает Portfolio owner. Тест не проверяет отклонение поздней публикации. Отдельные соседние тесты также сохранили названия failed refresh/recovery при прямой подстановке DB-state.
|
||||
4. **Риск: frontend race assertions не всегда стоят после завершения проигравшего.** StocksPage проверяет уже видимый новый результат сразу после освобождения старого ответа. В resource tests ожидается promise затвора, не всего HTTP/query lifecycle. Отсутствие надёжного completion barrier подтверждено; фактический false-green мутанта не запускался.
|
||||
5. **Факт: часть test oracles противоречит действующим поведенческим требованиям.** Portfolio tests требуют отсутствия предупреждения/retry; Events tests закрепляют фильтры и отсутствие retry; backend Events ожидает `data: null` для непересекающегося сохранённого прогноза. Это не просто недостаток покрытия: такие ожидания защищают другую модель UX/данных.
|
||||
6. **Факт: важные negative boundaries остаются неразличимыми.** Тест «publish fails» отклоняет upstream до начала публикации, а не запись внутри transaction. Auth cases не ловят различие UTF-8 bytes/characters и не проверяют refresh token как Bearer. Отсутствие теста не доказывает production-эксплуатацию.
|
||||
7. **Непроверенная семантика: отсутствующий блок MOEX.** Код и tests считают его failure, spec — нормальным отсутствием данных. Конфликт подтверждён, но без согласования provider semantics нельзя просто ослабить malformed-response guards. Решение — INVESTIGATE.
|
||||
|
||||
**Итог:** 10 actionable findings: 7 FIX, 1 SIMPLIFY, 1 условный DELETE и 1 INVESTIGATE; отдельно 3 KEEP записи о критических гарантиях. P0 findings нет. Цель — заменить слабые стимулы и неверные ожидания, а не увеличить число tests или coverage threshold.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Метод и границы подсчёта
|
||||
|
||||
Inventory выполнен один раз по исходным test-file patterns; уточняющие команды разделили первоначальные суммы по директориям:
|
||||
|
||||
```sh
|
||||
# backend
|
||||
rg --files src -g '*.spec.ts' | rg -c '\.spec\.ts$'
|
||||
rg --files test -g '*.e2e-spec.ts' | rg -c '\.e2e-spec\.ts$'
|
||||
# frontend
|
||||
rg --files src -g '*.test.ts' -g '*.test.tsx' | rg -c '\.test\.tsx?$'
|
||||
rg --files packages scripts -g '*.test.ts' -g '*.test.tsx' -g '*test*.mjs'
|
||||
```
|
||||
|
||||
| Область | Inventory | Что означает число |
|
||||
| --- | --- | --- |
|
||||
| backend `src` | 39 `*.spec.ts` | файлы colocated unit/component tests |
|
||||
| backend `test` | 20 `*.e2e-spec.ts` | HTTP/persistence/migration suites, включая отдельный read benchmark |
|
||||
| frontend `src` | 57 `*.test.ts(x)` | test files, включая `position-layout.browser.test.tsx` |
|
||||
| frontend `packages` | 6 `*.test.ts(x)` | UI/icons tests, включая `EntryCard.browser.test.tsx` |
|
||||
| frontend `scripts` | 2 `*test*.mjs` | tooling tests; только инвентаризированы, вне глубокого анализа 08 |
|
||||
|
||||
Поиск учитывает ignore rules `rg`, исключает dependencies/generated/build outputs. Рабочие деревья были чистыми. Это **число файлов, не число сценариев**: `it.each` и loops не развёртывались для метрик. Test-case total и проценты покрытия не вычислялись. 28 PASS относятся только к двум явно запущенным suites.
|
||||
|
||||
Конфигурации уровней сверены по `backend/package.json:123–139` и `frontend/vitest.config.ts:26–65`: Jest Node для backend unit, отдельные SQLite/HTTP suites, frontend JSDOM и настоящий Chromium для browser layout. Здесь не оценивались стоимость runner/setup, CI minutes или целесообразность их архитектуры.
|
||||
|
||||
### 3.2. Risk-based sampling matrix
|
||||
|
||||
| Flow | Исследованные тестовые owners | Production/contract boundary | Почему выбрано |
|
||||
| --- | --- | --- | --- |
|
||||
| Exact money | decimal, RUB parser, bank-card HTTP, bank summary sorting | BigInt decimal → application → SQLite/HTTP → frontend comparator | финансовая точность и независимо заданные edge values |
|
||||
| Auth/CAS/replay | AuthService unit, auth HTTP, frontend client/AuthProvider | JWT verification, conditional update, cookie response, session generation | серьёзный эффект удаления одного guard; сильные и слабые proofs |
|
||||
| Connection publication | local reads, connection HTTP, value history | local GET vs source request; generation/FK/conditional publish | отделить реальные fences от несостоявшихся races |
|
||||
| Async/read lifecycle | StocksPage, resource query, screen sync, initial availability | HTTP promises, query cancellation, request identity, timer boundaries | completion ordering, retries и ложные negative assertions |
|
||||
| Forecast/calendar | upcoming-events HTTP, ForecastRepository, MOEX parsing | saved forecast/window, dependency versions, complete source publication | неверный oracle, malformed-vs-empty и реальные stale-data guarantees |
|
||||
| Frontend screens | AccountPage/VM, EventsPage/filters, RouteErrorBoundary | route → VM → view, cached snapshot/error/recovery | проверить видимый пользовательский исход и integration seam |
|
||||
| API boundaries | configured auth/events HTTP, DTO pipe, OpenAPI, TbankClient mapping | guards/validation/status/schema/proto dates | различить wire contract и простое mock wiring |
|
||||
| Scheduler | все test paths по names + metadata cases | `tick → reserveHourly → walk → settleHourly` | новый критический owner, явный пробел executable coverage |
|
||||
| Browser fit | position-layout browser test + PositionSection | настоящий layout 375/1280 px | полезный тест геометрии, которую JSDOM не доказывает |
|
||||
|
||||
Shortlist отбирался по риску денежной/identity регрессии, смене владельца обновления, подозрительной асинхронности и стоимости устаревших ожиданий. Выборка намеренно включает хорошие примеры. Она не статистическая; findings не экстраполируются на каждый файл inventory. Большие suites прочитаны в релевантных диапазонах, с callers и setup, влияющими на stimulus.
|
||||
|
||||
**Вне глубокого охвата:** полный набор банковских UI-сценариев, все instrument/candle/ledger permutations, весь NCR parser, все theme/icons/package tests, scripts, миграционная совместимость/durability, browser cookie enforcement, actual gRPC queue scheduling/deadlines и длительность CI. Для них не заявляется отсутствие дефектов. Specs/docs использованы как независимые требования/решения, а не как исполняемые гарантии или автоматически правильные conventions.
|
||||
|
||||
### 3.3. Test Value Matrix
|
||||
|
||||
В этой таблице B = `backend/`, F = `frontend/`. Остальная часть каждого пути точная. «Ловит» означает вывод из stimulus/assertions, **не результат запущенной мутации**. Метка PASS используется только для реального целевого запуска.
|
||||
|
||||
| Test case / evidence | Claimed invariant | Given → executed stimulus | Observable assertion | Regression caught? | Action |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| B `src/integrations/tbank/decimal.spec.ts:5–22` | signed nano и большие суммы точны | `9007199254740993` + nano; отрицательная дробь; subtraction/multiply | точные ожидаемые decimal strings | да: Number coercion/sign/scale; **PASS** | KEEP TQ-012 |
|
||||
| B `src/modules/banking/bank-cards/domain/rub-money.spec.ts:4–57` | storage bounds, no rounding, positive input | independently заданные крайние kopecks и invalid strings → parse/format | точный BigInt/строка или throw | да: off-by-one signed64, дробная копейка, zero-positive; **PASS** | KEEP TQ-012 |
|
||||
| B `test/bank-cards.e2e-spec.ts:146–165` | точность проходит storage и HTTP | create/read карты `>2^53`, topup за край storage value, expenses 0.10/0.20 | точные balance strings в HTTP | да: serialization/storage/application truncation | KEEP TQ-012 |
|
||||
| F `src/entities/bank-card/api/summary.test.ts:9–37` | сортировка не теряет копейки и source order | huge amounts с разницей 0.01, ties, negatives → `sortBankSummary` | IDs в независимом порядке; source unchanged | да: Number comparator, mutation массива | KEEP TQ-012 |
|
||||
| B `src/modules/auth/application/auth.service.spec.ts:95–148` | один refresh winner, loser не отзывает session | два callers читают old hash через barrier → refresh; mock CAS | один fulfillment/401; expected hash передан; no revoke | да: application CAS/error wiring; не DB atomicity | KEEP TQ-011 |
|
||||
| B `test/app.e2e-spec.ts:161–213` | реальный CAS, replay rejection, usable winner | два HTTP refresh блокируются до CAS; `Promise.all` обоих | statuses 200/401, no loser cookie, старый token 401, winner 200, одна session | да: unconditional update/revocation/cookie clobber | KEEP TQ-011 |
|
||||
| F `src/shared/api/client.test.ts:92–123` | old identity retry не возвращает чужие данные | дождаться второго GET → generation change → release → await rejection | итоговый Promise отклонён `AbortError` | да: удаление post-retry generation check | KEEP |
|
||||
| F `src/features/auth/model/auth.test.tsx:189–275` | auth-cookie writes сериализованы в этой вкладке | pending refresh → logout/login → release → await всех операций | итоговая identity и порядок реальных MSW endpoints | да: late refresh overwrites login/logout; не browser cookie attributes | KEEP |
|
||||
| B `test/tbank-local-reads.e2e-spec.ts:489–514` | late portfolio fenced after replacement | deferred RPC установлен → только local GET → PUT → release неиспользованного promise | data null, DB count 0 | **нет stimulus публикации**, тест этого fence не различает | FIX TQ-001 |
|
||||
| B `test/tbank-local-reads.e2e-spec.ts:299–325` | failed refresh сохраняет old totals | seed успешного owner result → ручной failed work-state → local GET | old data; lastAttempt none; RPC count 1 | ловит часть read projection, не failed owner refresh | уточнить scope / заменить stimulus; TQ-001 |
|
||||
| B `test/tbank-local-reads.e2e-spec.ts:328–379` | recovery/coalescing concurrent discovery | abandoned row → 20 local GET; RPC forbidden; work завершён самим тестом | accounts видимы; no RPC | ловит passive reads, не recovery producer и не coalescing работы | переименовать/убрать unused deferred; TQ-001 |
|
||||
| F `src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–236` | новый search переживает late page | page GET + новый search → новый результат → release old | новый текст уже существует; старого ещё нет | completion loser не доказан; false-green mutant **UNVERIFIED** | FIX TQ-002 |
|
||||
| F `src/shared/api/resource-query.test.tsx:139–172` | observed result побеждает older GET | old handler started → await observe → release; await только затвора | current payload равен snapshot | часть cancellation path выполнена; end-to-end settlement old HTTP **UNVERIFIED** | FIX barrier TQ-002 |
|
||||
| F `src/shared/api/resource-query.test.tsx:174–209` | access loss очищает pending sibling | initial a loaded → a 404 → b release; await затвора | a/b data undefined; a isError | initial access reset различает; late sibling application не доказана | FIX barrier TQ-002 |
|
||||
| F `src/features/tbank-connection/model/screen-sync.test.tsx:57–79` | resources refresh together | один accounts resource → manual refresh | GET count 2 | ловит manual refetch одного owner; не multiple resources/timer/unmount | KEEP узкую гарантию; wider claim не подтверждён |
|
||||
| F `src/shared/lib/initial-availability.test.tsx:7–37,76–101` | wait budget и identity reset | fake time 99+1; rerender new identity | loading/unavailable/ready | да: deadline off-by-one, stale identity selection | KEEP |
|
||||
| F `src/pages/account/ui/AccountPage/AccountPage.test.tsx:751–855` | snapshot сохраняется «тихо» после 503 | initial data → invalidation → request count → следующий invalidation | суммы остаются, warning/retry отсутствуют | да для totals; неправильный oracle для recovery; timestamp selector устарел | FIX TQ-004 |
|
||||
| B `test/upcoming-events.e2e-spec.ts:321–340` | иной forecast range не выдаётся за прежний | saved forecast Feb → query Mar | `newRange.data === null` | различает range, но отвергает required successful empty fallback | FIX TQ-005 |
|
||||
| B `test/upcoming-events.e2e-spec.ts:619–640` | narrow filter не обновляет calculation time | saved Feb window → Feb 1–10 query | тот же calculatedAt; одна event; no RPC | да для overlap; не non-overlap/latest-success/shifted-window publication | KEEP и дополнить TQ-005 |
|
||||
| B `test/upcoming-events.e2e-spec.ts:344–368` | dependency CAS отказывает stale candidate | qty=3 candidate → реальный qty=9 write в seam перед resolve | first result old; next result `90.000000009` | да: удаление portfolio revision fence | KEEP TQ-013 |
|
||||
| B `src/integrations/moex/moex.client.spec.ts:121–191` | malformed calendar не становится complete | valid empty blocks + `coupons: undefined`/bad columns/rows → parser | MOEX_UNAVAILABLE | да, но отсутствие блока спорно объединено с malformed | INVESTIGATE TQ-006; malformed KEEP |
|
||||
| B `src/integrations/moex/moex.client.spec.ts:223–274` | все cursor pages, no repeated index | controlled response pages/index → реальный parser | две точные events или failure; starts 0/1 | да: потерянная page/repeated cursor | KEEP |
|
||||
| B `test/upcoming-events.e2e-spec.ts:540–599` | source failure сохраняет complete forecast | сохранить реальный parsed calendar → force invalid payload → await durable works | DB calendar/forecast и time прежние; failure observable | да для persistence fallback; семантика missing block спорна | KEEP malformed arm; TQ-006 |
|
||||
| B `src/common/dto-validation.spec.ts:13–29` | credentials required, email normalized | missing fields / ASCII-valid password → configured local ValidationPipe | reject или normalized DTO | да для этих inputs; не UTF-8 minimum/maximum semantics | FIX gap TQ-007 |
|
||||
| F `src/features/auth/ui/AuthForm/AuthForm.test.tsx:9–31` | confirmation mismatch не отправляется | заполнить несовпадающие пароли → click submit | видимая field error и onSubmit не вызван | да: removal confirmation guard; callback — допустимый seam | KEEP; TQ-007 не закрывает |
|
||||
| B `test/tbank-value-history.e2e-spec.ts:316–333` | «publish fails» сохраняет snapshot/points | existing points → `getPortfolio` rejects → await owner done | points прежние; snapshot не asserted | да для upstream failure; **не transaction rollback** | FIX TQ-008 |
|
||||
| B `test/tbank-operations-persistence.e2e-spec.ts:186–246` | group publication atomic | successful a/b → изменить staged a, оставить b incomplete → publish | rejection; прежние rows и coverage times; later success/revision stability | да: first interval writes rollback после incomplete b | KEEP TQ-013 |
|
||||
| B `test/tbank-operations-persistence.e2e-spec.ts:74–126` | separate reader видит old/new group | real parallel publish + 20 reads; reconnect reader | только 10/10 или 20/20; reopened 20/20 | полезно, но reader не гарантированно попадает внутрь любого write interleaving | KEEP вместе с deterministic rollback, не считать exhaustive race proof |
|
||||
| B `test/bank-cards.e2e-spec.ts:391–422` | replay не дублирует деньги/не оживляет tombstone | same intent дважды + другой expense → edit/delete/replay | balance 700/800, 409 stale version, 410 deleted replay | да: duplicate debit, lost independent write, resurrected operation | KEEP TQ-013 |
|
||||
| B `test/bank-cards.e2e-spec.ts:622–653` | refunds limited, rejected locks rollback | HTTP refund vs expense reduction; после settlement invalid refund date | один успех; исходная version после rejection; refund sum ≤ expense | да: race unsafe limit/transaction removal | KEEP TQ-013 |
|
||||
| B `src/modules/investments/tbank-sync/tbank-screen.spec.ts:4–73` | manual screen normalization/validation | pure normalizeScreen с descriptors/dates | normalized object или throw | да для функции, но у текущего request-flow нет запускающего consumer | conditional DELETE TQ-009 |
|
||||
| B `src/modules/investments/upcoming-events/upcoming-events.controller.spec.ts:8–18` | authenticated sub/account/query forwarded | прямой controller call с arbitrary sub → mock service | точные service arguments | да для seam mapping; не guards/HTTP/DTO/runtime response | KEEP малую seam-проверку |
|
||||
| B `src/integrations/tbank/tbank.client.spec.ts:174–239` | bounded retry, queue admission, auth failure | mock transport/queue, real `call` policy + fake 999/1 ms | request count и resolved/rejected result | да: policy bypass/retry excess; не реальный queue scheduler | KEEP, корректно назвать boundary |
|
||||
| F `src/app/routes/RouteErrorBoundary/RouteErrorBoundary.test.tsx:133–148` | безопасный fallback и navigation reset | throwing route → click healthy navigation | no technical details; healthy content | да: boundary reset/navigation regression | KEEP |
|
||||
| F `src/pages/account/ui/AccountPage/position-layout.browser.test.tsx:17–95` | stocks/bonds vertical, other responsive | real render at configured 375/1280 px → geometry read | ненулевая геометрия и relative positions | да: ошибочное число колонок; не весь visual/a11y contract | KEEP browser level |
|
||||
| F `src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx:229–290` | filters apply/reset/history | real route/MSW → picker/apply/back/reset | URL, period text, expected GET params | да для старого filter UI, которого current spec не требует | SIMPLIFY TQ-010 |
|
||||
| F `src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:172–190` | «quiet» error без retry | mocked error resource → rerender | Reset видим, Retry отсутствует | закрепляет противоположное required HTTP recovery | SIMPLIFY/FIX TQ-010 |
|
||||
|
||||
### 3.4. Critical Behavior Gaps
|
||||
|
||||
| Critical behavior | Что уже проверяется | Что остаётся без надёжного distinguishing proof | Самая дешёвая responsible boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| Hourly owner/lifecycle | pure metadata projection; individual source owners | реальные сроки/overlap/restart/partial failure/terminal settlement | controlled `tick` с fake upstream и настоящим repository; SQLite для reservation, TQ-003 |
|
||||
| Поздний portfolio старого подключения | connection replacement/FK; прямой old-attempt rejection | действительно started owner → replace → complete late owner | existing local-reads suite с await started + `work.done`, TQ-001 |
|
||||
| Late frontend result | новая search identity; initial access reset | assertion после завершения проигравшей операции, а не его затвора | существующие RTL/MSW suites, TQ-002 |
|
||||
| Cached Portfolio + visible metadata/retry | сохранение сумм; standalone status widget | route/container не теряет source; 503 warning и HTTP-only retry | существующий AccountPage integration test, TQ-004 |
|
||||
| Forecast filter/latest result | overlap filtering, same-scope CAS | non-overlap empty fallback и публикация нового полного shifted window | существующая SQLite/HTTP Events suite, TQ-005 |
|
||||
| Empty vs malformed source | malformed cursor/rows и сохранение старого complete | смысл отсутствующего блока, отдельно от damaged block | unit parser + один integration arm после решения, TQ-006 |
|
||||
| Auth trust boundary | valid access, absent access, refresh replay/CAS | UTF-8 boundary; refresh-as-Bearer; cookie flags; JWT/DB expiry в актуальном suite | существующий configured auth HTTP и local DTO/schema tests, TQ-007 |
|
||||
| Portfolio transaction rollback | upstream failure preserves points | ошибка после первого успешного write сохраняет snapshot и points вместе | SQLite repository publication со failure injection внутри transaction, TQ-008 |
|
||||
| Current events UX | старая форма периода и Reset | forecast calculation time, read-only list и Retry как reread того же resource | RTL/MSW existing Events suite, TQ-010 |
|
||||
|
||||
Отдельно: два `screen-sync` tests не продвигают 30-минутный timer и используют только один resource. Нельзя считать interval/multi-resource/unmount поведение проверенным. Это ограничение выборки, а не основание автоматически строить новый scheduler harness или отдельный finding.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
Для KEEP severity означает ущерб от удаления защищающей гарантии, **не текущий дефект**. Effort для KEEP — стоимость сохранить/правильно классифицировать proof. `Impact unmeasured` означает отсутствие измерения инцидентов, flaky frequency или затрат CI.
|
||||
|
||||
### TQ-001 — Late-portfolio test не запускает защищаемую публикацию
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-003, TQ-013.
|
||||
- **Evidence:** `backend/test/tbank-local-reads.e2e-spec.ts:489–514,69–76`; `backend/src/modules/investments/portfolio/presentation/portfolio.controller.ts:25–31`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:55–59,113–231,234–306`; `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31–76`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–146`.
|
||||
- **Claim:** deferred `getPortfolio` не потребляется stimulus теста. Нет source-owner request, late candidate и вызова publication fence, который якобы проверяется.
|
||||
- **Current mechanism:** GET вызывает `portfolio.read`, а тот читает access/saved data/metadata; PUT валидирует token и транзакционно заменяет connection, без запуска PortfolioService. Deferred освобождается, затем ожидается 20 ms и проверяется по-прежнему пустая DB. Scheduler зарегистрирован в этой suite, но тест не вызывает `tick` и не синхронизируется с минутным timer.
|
||||
- **Impact / actual cost:** удаление portfolio conditional-publication guard не обязано менять outcome этого case: publisher им вообще не вызван. Подтверждён структурный пробел stimulus; запуск такого мутанта не выполнялся. Impact unmeasured.
|
||||
- **Why now:** suite уже использует явный `portfolioService.request` в других cases; данный тест сохранил предположение, что обычный GET начинает работу.
|
||||
- **Minimum change:** сохранить отдельный полезный empty-membership assertion. До replacement вызвать existing `request` с текущей revision; потребовать ненулевой work и подтверждённый старт upstream; после replacement освободить RPC и **await `work.done`**. Затем проверить отсутствующую old-generation publication и неизменность new-generation data.
|
||||
- **Guarantees at risk:** connection isolation, empty ≠ missing, late financial writes. Не заменять локальный GET импортом ради восстановления старого теста.
|
||||
- **Counterarguments / trade-offs:** FK и old-attempt tests уже защищают некоторые случаи (`tbank-connection.e2e-spec.ts:113–161`, `tbank-value-history.e2e-spec.ts:264–313`). Они не делают этот case полноценной owner race. Если минутный tick случайно случится, это не контролируемый proof, а неявная зависимость от времени suite.
|
||||
- **Verification or rollback:** targeted existing SQLite suite; порядок started → replace → release → done → assertion обязателен. Откат правки теста не меняет production. Проверка удаления fence в рамках этого аудита запрещена и не выполнялась.
|
||||
|
||||
### TQ-002 — Frontend late-response assertions не имеют completion barrier проигравшего
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** Medium. **Effort:** S. **Related finding IDs:** TQ-001, TQ-011.
|
||||
- **Evidence:** `frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–236`; `frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts:65–100`; `frontend/src/shared/api/resource-query.test.tsx:139–209`; `frontend/src/shared/api/resource-query.ts:104–126`; `frontend/src/shared/api/client.ts:205–247`.
|
||||
- **Claim:** освобождение MSW затвора не эквивалентно завершению fetch body, continuation и React/query application. Финальные assertions допускают проверку состояния до этого завершения.
|
||||
- **Current mechanism:** после «Новый» stock test вызывает `oldPage.resolve`, затем `waitFor` на уже выполненное условие. Нет `old request started` assertion или ожидания завершения `changePage`. Resource tests ждут `blocked`, который разрешён самими tests; generated query/fetch продолжает собственную цепочку. `act` может обработать часть работы, но это не явное ожидание полного сетевого Promise.
|
||||
- **Impact / actual cost:** stale request guard/cancellation regression может не наблюдаться в момент assertion. Точное false-green расписание и mutant outcome — UNVERIFIED; частота flakiness не измерялась. Impact unmeasured.
|
||||
- **Why now:** эти cases названы regression tests именно late completion, а production имеет отдельные guards на success/error/finally и собственный response-body lifecycle.
|
||||
- **Minimum change:** в existing test probe сохранить awaited operations/settled marker для реального вызываемого Promise, либо использовать существующую наблюдаемую lifecycle boundary, подтверждающую завершение. Начало старого handler проверить до нового search; после release ждать old operation settlement и React flush; затем сравнить data/error/loading. MSW handler-return marker сам по себе ещё не гарантирует client application.
|
||||
- **Guarantees at risk:** request identity, cancellation/access-group cleanup; не добавлять глобальные epochs/HTTP registries ради теста.
|
||||
- **Counterarguments / trade-offs:** текущие `act`/MSW scheduling иногда доведут response до конца; correctness source guards присутствуют. Поэтому не заявляется, что existing test обязательно false-green. Сильный альтернативный образец — `client.test.ts:115–123`: ожидается итоговый request rejection, не только освобождение затвора.
|
||||
- **Verification or rollback:** targeted StocksPage/resource-query tests без сборки нового harness; финальное условие должно стать невозможным до settlement old operation. Source mutation не нужна для правки ordering; удаление guard — лишь описанный мысленный regression.
|
||||
|
||||
### TQ-003 — Нет executable proof часового координатора и durable reservation policy
|
||||
|
||||
- **Decision:** FIX. **Severity:** P1. **Confidence:** High. **Effort:** M. **Related finding IDs:** TQ-001, TQ-005.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–110,113–189,199–218`; `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:174–242`; `backend/test/upcoming-events.e2e-spec.ts:140–147`; `backend/test/tbank-value-history.e2e-spec.ts:47–53`; `backend/test/portfolio-structure.e2e-spec.ts:40–45`; `backend/src/modules/investments/investment-resource.mapper.spec.ts:45–117`; `specs/openspec/specs/hourly-investment-sync/spec.md:8–40`.
|
||||
- **Claim:** 59 inventoried backend test files не содержат прямого exercise `tick`/`reserveHourly`/`settleHourly` с assertion требуемого lifecycle. Полезные projection tests получают уже готовый hourly result.
|
||||
- **Current mechanism:** поиск `reserveHourly|settleHourly|\.tick\(|TbankHourlyScheduler` по `src`/`test` test patterns нашёл только три imports/overrides scheduler. Real owner сначала резервирует работу, запускает `walk` через setImmediate, затем ждёт источники и публикует result. Ordinary read/source tests проверяют другую boundary.
|
||||
- **Impact / actual cost:** регрессии часовых сроков, overlap, missed term, first launch и неверного successful settlement не имеют прямого regression oracle. Например, общий catch вызывает settle с прежним failures, а reserve policy сравнивает start с now: это конкретные ветви, которые нужен отличающий stimulus. Их production-исправление относится к другому аудиту. Impact unmeasured.
|
||||
- **Why now:** этот owner заменяет read-driven refresh для всех личных investment resources, то есть пробел влияет на основной способ обновления данных, а не опциональный helper.
|
||||
- **Minimum change:** одна таблица сценариев реального SQLite reservation с управляемым временем: first/due/not-due, running same process, interrupted other process, пропуск срока долгим обходом. Один controlled coordinator scenario: два accounts, успех одного и failure другого, сохранённый успешный snapshot и terminal partial result. Добавить error arm вне локального source catch; дождаться реальной terminal row. Mocks — только upstream/контролируемые внешние owners, не проверяемая reservation.
|
||||
- **Guarantees at risk:** single attempt, skip/no catch-up, сохранность partial successes, connection ownership, scheduler lifecycle. Не заменять durable CAS mock-map и не убирать isolation overrides в unrelated suites только ради coverage.
|
||||
- **Counterarguments / trade-offs:** indirect timer может сработать в длинном HTTP suite; отдельные source/metadata tests полезны. Без явных assertions результата обхода это не proof. Не требуется exhaustive scheduler permutation suite, inter-process lease или real upstream.
|
||||
- **Verification or rollback:** новое различающее поведение исполнять на existing isolated SQLite boundary; fake clock должен контролировать проверяемую eligibility, а work completion — явный terminal barrier. Согласовать long-walk policy до утверждения oracle. Не запускавшаяся в аудите проверка остаётся UNVERIFIED.
|
||||
|
||||
### TQ-004 — Portfolio tests защищают отсутствие recovery и не проверяют передачу source metadata
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-010.
|
||||
- **Evidence:** `frontend/src/pages/account/ui/AccountPage/AccountPage.test.tsx:751–855,859–895`; `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135`; `frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34`; `frontend/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx:55–59`; `frontend/src/shared/ui/SourceSyncStatus/SourceSyncStatus.tsx:31–47`; `frontend/src/shared/ui/SourceSyncStatus/SourceSyncStatus.test.tsx:23–26`; `specs/openspec/specs/frontend-data-resilience/spec.md:11–28`.
|
||||
- **Claim:** valuable cached-total assertions смешаны с противоположным требованиям oracle: предупреждение и Repeat должны отсутствовать. Route-level proof передачи `source` отсутствует.
|
||||
- **Current mechanism:** VM возвращает source; container его не передаёт; view умеет показывать timestamp/stale/attempt. Test проверяет отсутствие «Данные получены», тогда как current widget использует «Данные на». Поэтому этот negative regexp даже не различает появление актуального status widget. После 503 ожидание requestCount=2 не доказывает, что query уже перешёл в error.
|
||||
- **Impact / actual cost:** omission VM → view остаётся незамеченным, а требуемое предупреждение/retry может восприниматься как regression теста. Информация о stale/failed result теряется рядом с денежными суммами. Impact unmeasured.
|
||||
- **Why now:** существует нужный owner status widget и точная requirement о cached snapshot плюс неблокирующее предупреждение и HTTP-only retry.
|
||||
- **Minimum change:** переиспользовать existing integration case: fixture с осмысленными `lastSuccessfulAt/freshness/lastAttempt`; positive assertions текущего source label/state. 503 дождаться как terminal query outcome; суммы должны сохраниться, warning и Retry — появиться. Click Retry должен читать тот же GET и обновить data; source sync request недопустим.
|
||||
- **Guarantees at risk:** snapshot не исчезает и не превращается в initial skeleton; HTTP error не переписывает server `isStale`; identity switch не сохраняет чужие данные.
|
||||
- **Counterarguments / trade-offs:** «quiet» намерение могло означать отсутствие блокирующего loader или ручного импорта; это разумно. Оно не обосновывает скрытие freshness и HTTP recovery. Standalone widget test не ловит пропущенный prop в container.
|
||||
- **Verification or rollback:** RTL/MSW на реальном AccountPage route; не ограничиваться VM-only или presentation-only render. Production/UI согласовать с текущим behavioral requirement; исходники в этом аудите не менялись.
|
||||
|
||||
### TQ-005 — Forecast range oracle требует missing вместо сохранённого успешного empty result
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** M. **Related finding IDs:** TQ-003, TQ-006, TQ-013.
|
||||
- **Evidence:** `backend/test/upcoming-events.e2e-spec.ts:321–339,619–640`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:76–93,98–120`; `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:12–24,63–76`.
|
||||
- **Claim:** тест «never relabels another range» считает null правильным при непересечении сохранённого окна. Требование предписывает успешный пустой data с исходными calculation/source metadata. Сохранить исходную идентичность не означает потерять подтверждённый forecast.
|
||||
- **Current mechanism:** test сначала сохраняет February forecast, затем спрашивает March и ожидает null. Repository ищет fallback только среди пересекающихся scopes. Scope includes window; branch `!previous && fallback` возвращает fallback и при новом candidate. Narrow-window test проверяет только overlap и тот же timestamp.
|
||||
- **Impact / actual cost:** исправление non-overlap behavior ломает текущий oracle; новый полный candidate shifted window не имеет отдельного positive proof и может уступить старому. Пользователь получает missing/старую расчётную basis после успешной работы. Impact unmeasured.
|
||||
- **Why now:** с часовым обновлением default window движется; разные windows — обычный сценарий, а не только ручной date filter.
|
||||
- **Minimum change:** разделить два сценария на existing SQLite/HTTP boundary: (a) нет нового полного candidate, запрос вне saved window → empty events с прежними calculatedAt/source identity; (b) появился новый полный candidate с другой quantity/source version и shifted window → новый forecast с новой расчётной basis. Не переименовывать старый forecast новым scope/time.
|
||||
- **Guarantees at risk:** membership/portfolio/source revision CAS, точные amounts, fallback only complete, отсутствие fake recalculation time.
|
||||
- **Counterarguments / trade-offs:** old test правильно запрещает relabelling; это KEEP гарантия, которую надо сохранить новым expected shape. Выбор «последнего полного» требует явного ordering; внедрение новой repository abstraction не требуется. HTTP route spelling в spec местами старое — finding основан на содержательной semantics окна, не на совпадении имени endpoint.
|
||||
- **Verification or rollback:** оба сценария должны различать правильный итог независимо от ORM/query order; existing CAS test TQ-013 остаётся. Expected contract уточнить до source fix; mutations в аудите не выполнялись.
|
||||
|
||||
### TQ-006 — Missing MOEX block объединён с malformed response в двух уровнях тестов
|
||||
|
||||
- **Decision:** INVESTIGATE. **Severity:** P2. **Confidence:** High для конфликта; provider semantics UNVERIFIED. **Effort:** S. **Related finding IDs:** TQ-005, TQ-013.
|
||||
- **Evidence:** `backend/src/integrations/moex/moex.client.spec.ts:101–118,121–191,223–274`; `backend/src/integrations/moex/moex.client.ts:119–158,182–197`; `backend/test/upcoming-events.e2e-spec.ts:540–599`; `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:78–84`.
|
||||
- **Claim:** unit table и настоящий-parser integration arm оба требуют failure для отсутствующего блока; spec требует normal no-data. Тесты не являются независимым подтверждением нужности этого правила.
|
||||
- **Current mechanism:** `parseRows(undefined)` падает так же, как non-array columns/data или damaged row. Calendar owner сохраняет предыдущую DB source/forecast на failure. Empty arrays и 404 уже трактуются отдельно как normal absence.
|
||||
- **Impact / actual cost:** зелёные suites закрепляют противоречащий spec outcome; обратная механическая правка может ошибочно объявить неполный calendar подтверждённо пустым. Impact unmeasured.
|
||||
- **Why now:** distinction определяет, можно ли безопасно заменить complete calendar/forecast новым результатом, а не только текст ошибки.
|
||||
- **Minimum change:** согласовать missing-block semantics на сохранённом provider payload/первичном контракте без real banking calls. После решения выделить отсутствующий block в свой case; malformed columns/rows/cursor оставить failure. Отразить принятое ожидание и unit, и persistence level.
|
||||
- **Guarantees at risk:** полная pagination, no partial-success, сохранность последнего complete forecast, no fabricated zero payments.
|
||||
- **Counterarguments / trade-offs:** текущий failure может быть сознательной защитой от изменения ISS shape. Тогда меняется behavioral requirement, а не ослабляются guards. Наличие обоих уровней оправдано: unit доказывает parser, integration — сохранность DB fallback.
|
||||
- **Verification or rollback:** offline fixture подтверждает форму missing block; targeted unit и один real-parser integration arm. До решения обе защиты KEEP; production не изменён.
|
||||
|
||||
### TQ-007 — Auth suite не различает важные входные и token-role boundaries
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-011.
|
||||
- **Evidence:** `backend/src/common/dto-validation.spec.ts:13–29`; `backend/test/app.e2e-spec.ts:51–90,161–214,216–261`; `backend/src/modules/auth/application/auth.service.spec.ts:60–148`; `backend/src/modules/auth/presentation/auth.dto.ts:43–54,66–74`; `backend/src/modules/auth/infrastructure/jwt.strategy.ts:13–25`; `backend/src/modules/auth/presentation/auth.controller.ts:113–137`; `frontend/src/features/auth/model/auth.schema.ts:36–56`; `frontend/src/features/auth/ui/AuthForm/AuthForm.test.tsx:9–31`; `specs/openspec/specs/user-authentication/spec.md:10,43–60,83–96`.
|
||||
- **Claim:** фактический auth shortlist не проверяет UTF-8 byte boundaries, запрещённый refresh-as-Bearer и ключевые cookie attributes; удаление соответствующего application/config guard не различается current assertions.
|
||||
- **Current mechanism:** credentials case использует ASCII password; frontend case — confirmation mismatch. Backend минимум задан MinLength, maximum — byte validator; frontend обе границы считает TextEncoder. Ровно шесть кириллических букв дают 12 UTF-8 bytes, но не 12 символов. HTTP tests используют valid access и absent access; cookie проверяется для rotation/clear, не HttpOnly/SameSite/path/host-only flags. Поиск auth expiry/token-role/cookie assertions в `src` unit и `test/*.ts` не нашёл соответствующих negative cases.
|
||||
- **Impact / actual cost:** существующее несовпадение password semantics не имеет regression case. Пропущенные role/expiry/cookie tests — риск будущей регрессии, не доказательство существующего обхода auth: production type guard и expiry config присутствуют. Impact unmeasured.
|
||||
- **Why now:** это конкретные trust-boundary distinctions, явно требуемые current auth spec; ASCII happy path не различает конкурирующие правила.
|
||||
- **Minimum change:** добавить маленькую boundary table в existing DTO/schema tests: 11/12 и 72/73 UTF-8 bytes с multibyte fixture и сохранением исходного password. В существующем configured HTTP auth flow использовать настоящий refresh token как Bearer → 401. На уже имеющемся successful response проверить cookie flags/path/no Domain. Следующим risk-driven шагом — signed-expired access/refresh и DB-expired session; не создавать тест каждого decorator.
|
||||
- **Guarantees at risk:** server runtime validation остаётся authoritative; refresh не становится access; bad refresh не очищает winner cookie; maximum не допускает bcrypt truncation semantics.
|
||||
- **Counterarguments / trade-offs:** JWT/bcrypt/class-validator имеют собственные tests, но application выбирает role/expiry/options и связывает два независимых validators. Проверять весь framework не нужно; byte-vs-character fixture и application token-role case оправданы именно собственным контрактом. Browser enforcement cookie attributes этим HTTP test полностью не доказывается.
|
||||
- **Verification or rollback:** local boundary table + targeted configured auth HTTP. Подтверждение wire/runtime ошибки byte minimum доступно самым дешёвым DTO case; в этом аудите он не создавался/не запускался. Production исправляется отдельным изменением с согласованным contract.
|
||||
|
||||
### TQ-008 — «Publish fails» отклоняет upstream до transaction и не проверяет snapshot
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-001, TQ-012, TQ-013.
|
||||
- **Evidence:** `backend/test/tbank-value-history.e2e-spec.ts:99–111,316–333`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:265–291`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–183`.
|
||||
- **Claim:** имя утверждает сохранность snapshot и points при publish failure, но публикация вообще не начинается. Assertions сравнивают только value points.
|
||||
- **Current mechanism:** `getPortfolio` rejects внутри Promise.all; execution переходит в fail branch до normalize/repository.publish. Реальная transaction сначала claim-ит work, пишет Portfolio, затем history/points. Именно частичный write/rejection этот test не осуществляет.
|
||||
- **Impact / actual cost:** removal transaction wrapper либо failure после Portfolio upsert может повредить связность snapshot/points без изменения этого upstream-failure case. Это мысленная регрессия; actual mutant не запускался. Impact unmeasured.
|
||||
- **Why now:** value history добавляет несколько persisted outputs к одному publish; тест уже заявляет их совместную сохранность, но наблюдает только один.
|
||||
- **Minimum change:** оставить и честно назвать existing upstream-failure case. Один новый SQLite publication case должен выполнить первый реальный write и затем вызвать контролируемую ошибку внутри transaction (например, temporary DB trigger, удаляемый в teardown). Сравнить previous Portfolio payload/dataRevision/time, history points и согласованный work outcome. Не mock-ить весь publish.
|
||||
- **Guarantees at risk:** атомарность snapshot + points, dedup attempt/currency, original source time, fail-state без частично обновлённых данных.
|
||||
- **Counterarguments / trade-offs:** FK/transaction в production есть; дефект их работы не утверждается. Другие bank/operations rollback tests используют другие repository owners и не заменяют этот proof. Existing upstream failure case полезен и не удаляется.
|
||||
- **Verification or rollback:** isolated SQLite suite; fault обязан происходить после подтверждённой записи внутри tx, затем independently прочитать DB/reopen при необходимости. Удалить fault в teardown; не использовать application DB и не добавлять audit executable script.
|
||||
|
||||
### TQ-009 — Manual-screen normalization tests обслуживают недоступный request-flow
|
||||
|
||||
- **Decision:** DELETE после подтверждения retirement владельцем. **Severity:** P3. **Confidence:** High для текущего call graph. **Effort:** S. **Related finding IDs:** TQ-003.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-screen.spec.ts:1–75`; `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78`; `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–40`; `backend/test/openapi-contract.e2e-spec.ts:27–55`; `specs/openspec/specs/tbank-data-sync/spec.md:9–14`.
|
||||
- **Claim:** normalizeScreen unit cases проверяют manual-command descriptor boundary, у которой в текущем source/test consumer graph нет запускающего production caller.
|
||||
- **Current mechanism:** normalizeScreen вызывается только `TbankSyncService.request`; все references самого сервиса — declaration/provider/controller status. Controller предоставляет GET status, не request; OpenAPI test прямо требует отсутствия collection command path. Hourly owner использует другую reservation/flow.
|
||||
- **Impact / actual cost:** поддерживается тестовая модель запрещённой manual sync capability. Runtime/CI экономия not measured; минимум снижается cognitive/maintenance load при окончательном retirement.
|
||||
- **Why now:** новые нужные tests должны защищать hourly contract, а не расширять прежний manual request vocabulary.
|
||||
- **Minimum change:** согласовать retirement с production owner из audit 04/06; удалить именно `tbank-screen.spec.ts`, не заменяя тестами того же недоступного flow. Source retirement — отдельная задача, не выполненная этим аудитом.
|
||||
- **Guarantees at risk / что перестанет проверяться:** нормализация query/ticker; canonical categories/order; rejection отсутствующих identities/лишних descriptor fields; inclusive event calendar и invalid/default-period rejection **в manual descriptor API**. Это не active HTTP query validation, не auth, не hourly reservation и не Ledger planner.
|
||||
- **Counterarguments / trade-offs:** если request — явно поддерживаемый programmatic API с consumer вне проверенного дерева, DELETE заменяется KEEP. Такого consumer в `src`/`test` не найдено. Нельзя по этой находке удалять `planInvestmentScreen`, у него иной active consumer.
|
||||
- **Verification or rollback:** повторить consumer search перед удалением, сохранить existing HTTP/calendar/owner tests; восстановление test file из Git полностью обратимо. До подтверждённого retirement эта рекомендация условная.
|
||||
|
||||
### TQ-010 — Events tests закрепляют старую форму периода и отсутствие HTTP retry
|
||||
|
||||
- **Decision:** SIMPLIFY. **Severity:** P2. **Confidence:** High для расхождения. **Effort:** M. **Related finding IDs:** TQ-004, TQ-005.
|
||||
- **Evidence:** `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx:159–291`; `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:63–79,172–190`; `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:19–33,62–75,92–123`; `frontend/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageLoaded.view.tsx:59–98`; `frontend/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageError.view.tsx:13–23`; `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:86–107`.
|
||||
- **Claim:** наиболее развитые current tests защищают интерактивный filter UI и Reset вместо Retry, тогда как existing spec требует read-only forecast list без фильтров и с HTTP-only recovery.
|
||||
- **Current mechanism:** VM хранит applied/draft periods и maxDate, отдаёт events, но не exposes calculatedAt/original forecast window; view показывает date picker/Apply/Reset. Error test явно отвергает «Повторить». MSW filter tests доказывают старые URL transitions, но не current forecast presentation.
|
||||
- **Impact / actual cost:** UX migration удерживается устаревшими assertions; required timestamp/coverage explanation/retry не имеют route-level oracle. Поддерживается периодная state machine, которая не нужна сохранённому current screen contract. Impact unmeasured.
|
||||
- **Why now:** spec уже различает query window API и read-only screen. Нет необходимости переиспользовать Ledger-подобную форму периода для этого экрана.
|
||||
- **Minimum change:** вместе с согласованной screen migration заменить filter-interaction сценарии несколькими existing RTL/MSW cases: sorted saved list и calculatedAt/original window; empty complete; missing/error; complete fallback + failed attempt; Retry читает тот же GET. Сохранить semantic event amount/offer/identity и доступное status text. Transport `from/to` tests не удалять автоматически: API filtering остаётся отдельным контрактом.
|
||||
- **Guarantees at risk:** deep-link account scope, inclusive UTC filtering API, cached full forecast, no manual import, accessible statuses. Удаление формы не должно удалить эти гарантии вместе с test file.
|
||||
- **Counterarguments / trade-offs:** если продукт сознательно сохраняет filters, сначала согласовать изменение current spec и forecast-source UX. Сам факт слова «MUST» не доказывает оптимальность screen decision; однако code/test противоречие уже факт. Глубокий UI/source redesign относится к audit 05, здесь проверен только test oracle/coverage.
|
||||
- **Verification or rollback:** existing route/MSW tests должны проверять видимый контент, а не private VM field shape. Reuse existing cases/handlers; новый browser runner не нужен. Ошибка source и transport должны оставаться раздельными.
|
||||
|
||||
### TQ-011 — KEEP: реальные auth CAS и session-generation proofs дополняют unit tests
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P1. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-002, TQ-007.
|
||||
- **Evidence:** `backend/test/app.e2e-spec.ts:161–213`; `backend/src/modules/auth/application/auth.service.spec.ts:95–148`; `backend/src/modules/auth/application/auth.service.ts:81–114,166–200`; `frontend/src/shared/api/client.test.ts:92–123`; `frontend/src/features/auth/model/auth.test.tsx:189–275`; `frontend/src/features/auth/model/auth.vm.ts:41–115,121–140`.
|
||||
- **Claim:** unit mock CAS, real SQLite HTTP race и frontend identity/cookie-write ordering защищают разные failure modes; это не бесполезное повторение одного сценария.
|
||||
- **Current mechanism:** unit barrier обеспечивает два reads и проверяет expected hash/error handling. HTTP barrier пропускает оба real sign/refresh paths к SQLite CAS, ждёт обоих responses и последовательно проверяет replay/winner. Frontend tests ждут реальные operation promises и проверяют identity после late completion.
|
||||
- **Impact / actual cost:** ловятся unconditional rotation, session revocation loser-ом, loser Set-Cookie и resurrection old user. Стоимость исполнения этих suites not measured; runtime assertions по статике значимы.
|
||||
- **Why now:** simplification и тестовые gaps TQ-002/TQ-007 не должны вести к замене сильного proof более дешёвым, но неэквивалентным mock wiring.
|
||||
- **Minimum change:** сохранить cases/барьеры. При переименовании прямо обозначать DB vs orchestration vs frontend generation scope. Не добавлять cross-tab protocol, которого current contract не требует.
|
||||
- **Guarantees at risk:** один winner, reusable winner session, no cookie clobber, in-memory identity isolation.
|
||||
- **Counterarguments / trade-offs:** JWT signature boundary — уместный test seam для ordering, не assertion private call order. Это same-process HTTP race, не доказательство multi-process deployment или реального browser cookie enforcement.
|
||||
- **Verification or rollback:** targeted auth HTTP plus existing frontend cases при изменении этого owner. Аудит их runtime не запускал; hypothetical removing expected-hash predicate изменяет содержательный outcome, а не только mock invocation.
|
||||
|
||||
### TQ-012 — KEEP: money tests имеют независимые oracles и правильные complementary levels
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P1. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-007, TQ-008, TQ-013.
|
||||
- **Evidence:** `backend/src/integrations/tbank/decimal.spec.ts:5–37`; `backend/src/modules/banking/bank-cards/domain/rub-money.spec.ts:4–57`; `backend/src/common/decimal.ts:19–48,59–83`; `backend/src/integrations/tbank/decimal.ts:9–13`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`; `backend/test/bank-cards.e2e-spec.ts:146–165`; `frontend/src/entities/bank-card/api/summary.test.ts:9–37`; `frontend/src/entities/bank-card/api/queries.ts:13–25`.
|
||||
- **Claim:** финансовые expected results не вычислены тем же production helper; границы точно отличают float/coercion/range regressions. Unit + real DB/HTTP + frontend comparator не взаимозаменяемы.
|
||||
- **Current mechanism:** explicit BigInt/string fixtures, boundary values ±signed64, forbidden fractional kopecks, zero/negative operation guard, computed sum beyond storage bounds. HTTP проверяет persist/serialize/application, frontend comparator различает копейки больших чисел и non-mutating ties.
|
||||
- **Impact / actual cost:** сохраняются точные деньги и no rounding/no fake zero; **две pure suites реально прошли, 28 tests**. Стоимость remaining layers not measured.
|
||||
- **Why now:** эти small tests дают больше защиты, чем рост coverage на passthrough DTOs; сокращать их из-за похожих inputs нельзя.
|
||||
- **Minimum change:** оставить existing tables. Новую property-testing dependency или suite для каждого arithmetic helper текущие findings не обосновывают. Finance regression добавлять по отличающему risk, не ради 100% coverage.
|
||||
- **Guarantees at risk:** precision, nanos/sign, storage input bound, вычисления beyond individual stored-value bound, per-currency semantics.
|
||||
- **Counterarguments / trade-offs:** Number допустим для округляемого display percentage (`frontend/src/entities/portfolio/model/display.ts:7–24`), это не тот же инвариант, что exact money sorting. Длинный fraction case проверяет собственный formatter; его runtime cost отдельно не выделялся.
|
||||
- **Verification or rollback:** фактический targeted command записан в metadata; он не подтверждает всю финансовую систему. При persistence change сохранить HTTP exact-value proof отдельно от pure unit result.
|
||||
|
||||
### TQ-013 — KEEP: deterministic rollback, dependency CAS и replay проверяют реальные данные
|
||||
|
||||
- **Decision:** KEEP. **Severity:** P1. **Confidence:** High. **Effort:** S. **Related finding IDs:** TQ-001, TQ-005, TQ-006, TQ-008, TQ-012.
|
||||
- **Evidence:** `backend/test/tbank-operations-persistence.e2e-spec.ts:186–246,292–350`; `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:703–818,820–839,932–944`; `backend/test/upcoming-events.e2e-spec.ts:344–368`; `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:80–145`; `backend/test/bank-cards.e2e-spec.ts:391–422,622–653`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:152–195,214–240`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–59`.
|
||||
- **Claim:** это tests собственных transaction/publication/idempotency invariants, не просто проверка Prisma/SQLite «работает».
|
||||
- **Current mechanism:** в operations publish первая изменённая группа реально записывается, затем incomplete marker второй группы throws; independently прочитанные rows/coverage должны остаться старыми. Forecast case меняет DB revision в контролируемом seam перед original resolve. Banking HTTP replay проверяет persisted balance/tombstone, а invalid refund date возникает после version-lock write внутри transaction.
|
||||
- **Impact / actual cost:** rollback failure, stale calculated publication, duplicate debit/replay resurrection становятся наблюдаемыми. Impact runtime/CI not measured; не утверждается exhaustive coverage всех interleavings.
|
||||
- **Why now:** похожие «failed refresh» names не делают все failure tests равноценными. Эти cases обеспечивают реальные данные/порядок, которых нет в TQ-001/TQ-008.
|
||||
- **Minimum change:** сохранить existing cases и asserted financial outcomes. Private seam в forecast допустим как контролируемая интервенция, потому что test вызывает original repository и наблюдает HTTP/DB итог. Не заменять conditional publication проверкой `publish called`.
|
||||
- **Guarantees at risk:** group atomicity, revision stability, connection/member/source binding, exact reconciliation, replay protection и refund limit.
|
||||
- **Counterarguments / trade-offs:** отдельный reader loop не гарантирует попасть в середину каждого scheduling interleaving; claim должен быть ограничен. Deterministic post-first-write rollback и controlled revision-change cases дают более сильный distinguishing proof и сохраняются независимо от этого reader sample.
|
||||
- **Verification or rollback:** existing isolated SQLite suites при соответствующем source change. Их запуск не выполнен в аудите; вывод основан на точном control flow до throw и независимых observed records.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Объект | Обоснование и условие | Что обязательно сохраняется |
|
||||
| --- | --- | --- | --- |
|
||||
| DELETE | `tbank-screen.spec.ts`, TQ-009 | текущий normalization consumer — недоступная manual command; только после retirement confirmation | active HTTP date/access tests, hourly owner и Ledger planner |
|
||||
| SIMPLIFY | Events filter-interaction tests, TQ-010 | заменить прежний UX oracle current read-only forecast behavior | event meaning/amount, account scope, API date semantics, cached result/retry |
|
||||
| FIX | race stimulus/barriers, TQ-001/002 | started producer и completed loser вместо unused deferred/early negative | generation/identity guards и real observed data |
|
||||
| FIX | hourly proof, TQ-003 | actual coordinator/reservation/result boundary | durable reservation, no overlap/catch-up, partial-success preservation |
|
||||
| FIX | Portfolio/forecast/auth/rollback oracles, TQ-004/005/007/008 | existing suites можно сделать различающими без нового framework | monetary accuracy, HTTP read-only, CAS, validation, transaction |
|
||||
| KEEP | exact money layers, TQ-012 | разные failure modes: pure math/storage/wire/sorting | большие/отрицательные значения и независимо заданные expected results |
|
||||
| KEEP | real auth CAS + frontend generations, TQ-011 | две операции реально начаты и дожданы; winner проверен после replay | conditional update/no loser cookie/no resurrected identity |
|
||||
| KEEP | transaction/CAS/replay cases, TQ-013 | actual DB writes перед failure или изменением dependency | old complete group, work/source versions, tombstones, refund limits |
|
||||
| KEEP | controller argument seam test | 20-line arbitrary-user mapping проверяет собственный adapter; HTTP guards другим уровнем | authenticated sub/account/query routing |
|
||||
| KEEP | TbankClient retry/date seam tests | собственная calendar/protocol/retry policy, даже при mock transport | inclusive → half-open UTC, retry bound, no auth retry |
|
||||
| KEEP | real browser layout case | actual geometry различает колонку/row; JSDOM не даёт эквивалентный oracle | 375/1280 responsive behavior |
|
||||
| INVESTIGATE | missing MOEX block, TQ-006 | semantic conflict, no trusted provider payload in audit | malformed/cursor/partial-calendar safeguards |
|
||||
|
||||
**Применение ponytail-audit:** `delete: manual-screen normalization tests после retirement недоступного command-flow; replacement: ничего для прежней capability. [backend/src/modules/investments/tbank-sync/tbank-screen.spec.ts]`
|
||||
|
||||
Размер этого условного кандидата — **75 физических строк**, по numbered Read существующего файла. Для него отдельно: `net: -75 lines, -0 deps possible.` Это размер только удаляемого test file, **не прогноз net diff всех рекомендаций**; source cleanup, replacement tests и CI savings не измерялись. Массовое удаление controller/DTO/mock tests только по внешнему виду не обосновано.
|
||||
|
||||
### High-value KEEP examples
|
||||
|
||||
- **Auth HTTP race:** обе операции проходят barrier, SQLite выбирает winner, обе завершены до assertion; replay старого cookie и последующий winner refresh различают «одна ошибка» и «повреждена победившая session».
|
||||
- **Operations group rollback:** incomplete second interval проверяется после обработки первого, поэтому removal transaction меняет visible financial group/coverage, а не только количество calls.
|
||||
- **Forecast dependency CAS:** DB quantity/revision меняются до original resolve; old fallback и next exact amount отличают отказ stale candidate от просто вызванного repository method.
|
||||
- **Money:** expected maximum kopecks и decimal strings независимо заданы; roundtrip отдельно проверяет BigInt и output, не только `format(parse(x)) == x`.
|
||||
- **Bank replay/tombstone:** одинаковый intent и независимый expense одновременно не должны ни двойно списывать деньги, ни терять второй intent; replay после delete не оживляет operation.
|
||||
- **Browser layout:** реальные ненулевые bounds и относительное положение карточек; это оправданный browser test своего layout, а не тест декларации Mantine.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| Рекомендация | Ожидаемый класс пользы | Измерение | Обязательная regression boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| TQ-001/002 | меньше ложной уверенности/недетерминированного расследования races | false-green rate, flaky rate, agent tokens: not measured | started + completed real operation и observed data |
|
||||
| TQ-003 | предотвращение потери refresh/lifecycle regressions | incidents/CI minutes: not measured; suite может добавить время | DB reservation + terminal hourly result, fake upstream |
|
||||
| TQ-004/005/010 | меньше поддержки устаревшего поведения; лучше user-visible guarantees | maintenance time/UX impact: not measured | full route/resource result, original calculation metadata, HTTP-only retry |
|
||||
| TQ-006 | избежать неверного confirmed-empty либо ложного failure | provider failure frequency: not measured | offline payload semantics + real persistence fallback |
|
||||
| TQ-007/008 | закрытие конкретных trust/storage boundary blind spots | security/storage failure frequency: not measured | configured HTTP/DTO и post-write SQLite rollback |
|
||||
| TQ-009 | cognitive/maintenance load; удаление orphan scenario | candidate file: 75 физических строк; время исполнения not measured | повторная проверка consumer graph, active boundaries KEEP |
|
||||
| TQ-011/012/013 | сохранить наиболее ценные distinguishing proofs | только 2 pure suites/28 PASS; остальные runtime UNVERIFIED | complementary unit + true integration; не mock-only replacement |
|
||||
|
||||
Runtime production оптимизация этим аудитом не предлагалась. Проценты ускорения/сокращения покрытия/CI или общий net LOC не прогнозируются. Новые dependencies, runner scripts, generic factories и mutation framework не нужны для рекомендованных минимальных изменений.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
Максимум десять шагов; порядок — value/risk/effort, а не число замечаний.
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **TQ-001:** заменить unused-deferred late-portfolio stimulus existing source request, проверить started и await work.done. Empty membership оставить отдельной observable гарантией.
|
||||
2. **TQ-002:** добавить completion barrier real old operation в StocksPage/resource race cases; уже true new-result assertion не использовать как barrier.
|
||||
3. **TQ-004:** обновить existing route/MSW oracle: source metadata + сохранённые totals + warning + click HTTP retry; дождаться actual query error, не request counter.
|
||||
4. **TQ-007:** добавить independently expected multibyte boundaries и refresh-as-Bearer HTTP case; расширить cookie assertions на существующем success response.
|
||||
5. **TQ-008:** честно назвать upstream-failure test и добавить один post-first-write rollback case с snapshot + points, используя isolated SQLite.
|
||||
|
||||
### Нужны подтверждённый behavioral oracle или решение владельца
|
||||
|
||||
6. **TQ-003:** построить минимальный executable proof current hourly owner: reservation time table и один controlled partial/error walk. Сначала закрепить missed-term semantics; новый framework не нужен.
|
||||
7. **TQ-005:** согласовать latest saved forecast selection; заменить null expectation на successful empty fallback и добавить shifted-window/new-complete candidate distinction. Existing CAS/fallback tests KEEP.
|
||||
8. **TQ-010:** вместе с screen owner упростить Events test set под read-only current UX. API `from/to`, event meaning и source-error fallback не удалять вместе с filter UI.
|
||||
9. **TQ-006:** решить missing-block semantics по trusted offline payload/контракту; затем разделить absence/malformed unit cases и сохранить persistence negative proof.
|
||||
10. **TQ-009:** подтвердить отсутствие внешнего programmatic consumer manual command; после retirement удалить orphan normalization test. При наличии такого consumer — KEEP и явно определить контракт.
|
||||
|
||||
Полноценный ADR нужен только если меняется принятое продуктовое решение (например, сохраняются event filters вопреки текущей spec) или provider completeness semantics. Для исправления test stimulus, ожидания Promise и independent boundary fixture отдельный ADR не нужен.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **Audit 06/13:** hourly catch/settlement/polling и first-launch/missed-term поведение — `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–110,178–218`, `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:174–219`; здесь исследован только отсутствующий regression proof.
|
||||
- **Audit 05:** Portfolio metadata/recovery и Events filter UI owner — `frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34`, `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts:19–123`; полноценный UI redesign не выполнялся.
|
||||
- **Audit 04/06:** retirement недоступного manual command — `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–78`, `backend/src/modules/investments/tbank-sync/tbank-sync.controller.ts:31–40`; тестовый кандидат TQ-009 зависит от решения owner.
|
||||
- **Audit 09/11:** отсутствие timer/multi-resource proof не решать изменением runner/CI; `frontend/src/features/tbank-connection/model/screen-sync.test.tsx:21–79` — отдельная quality boundary, стоимость инфраструктуры здесь не оценивалась.
|
||||
- **Audit 15:** accepted ADR 019 и current behavioral specs расходятся с частью UI/parser/test expectations; `docs/adr/019-hourly-reads-and-online-instrument-reference.md:9–29`, Events/Auth specs — нужна reconciliation решений, не дополнительный обязательный workflow.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Current evidence | Самый дешёвый способ проверки | Что изменить в выводе |
|
||||
| --- | --- | --- | --- |
|
||||
| Есть ли поддерживаемый consumer `TbankSyncService.request` вне checkout? | в `src`/`test` найден только unused request owner; controller status-only | спросить владельца capability / повторить consumer search перед retirement | external consumer → TQ-009 KEEP вместо DELETE |
|
||||
| Missing ISS block означает supported absence или повреждение/изменение API? | tests/source failure vs spec no-data | trusted сохранённый JSON/первичный provider contract offline; никаких real personal APIs | выбрать unit/integration oracle TQ-006 и при необходимости обновить решение |
|
||||
| Принят ли current Events read-only UX или filters намеренно остаются? | current spec no filters/retry vs source/tests filters/reset | решение screen owner по existing spec/accepted ADR | TQ-010 simplification либо explicit amended contract; negative retry не оставлять молча |
|
||||
| Как выбирать последний complete forecast между окнами? | fallback ищет overlap, candidate может уступить old scope | existing SQLite suite с двумя independently distinguishable scopes/amounts/times | TQ-005 positive selection oracle без relabelling |
|
||||
| Какой long-walk/missed-term outcome действителен? | spec 10:00 → после 11:00 completion → следующий ≥12:00 | table `reserveHourly` на real isolated DB с контролируемыми start/finish/due | TQ-003 expected timing; конфликт source не исправлять по случайному аналогичному test |
|
||||
| При удалении stale guard текущий frontend test действительно false-green? | completion old operation не ожидается явно | при отдельной test-fix задаче сначала explicit started/settled instrumentation existing test; mutation только по отдельному разрешению | сейчас Medium risk; не превращать статическую гипотезу в измеренный факт |
|
||||
| Какие suites актуально проходят в runtime? | запущены только две денежные pure suites | owners запускают соответствующие existing targeted suites после изменений | current report не означает all tests PASS |
|
||||
|
||||
**Минимальный порядок последующей верификации:** safe pure auth/decimal boundary → targeted RTL/MSW owner tests → isolated SQLite publication/reservation/auth HTTP → browser только для изменяемой browser-specific гарантии. Использовать локальные `.nvmrc` и existing runner safeguards. Полный verify нужен при будущих code changes по affected repository conventions, а не как побочный эффект read-only аудита.
|
||||
|
||||
**Проверка завершения аудита:** новый отчёт просмотрен на структуру, exact evidence и границы claims; `git diff --check` в checkout не выявил whitespace errors. Для нового ignored-файла отдельно выполнен `git diff --no-index --check /dev/null audit-results/08-test-quality-and-effectiveness-report.md`; замечаний нет. Подсчёт заголовков `^### TQ-[0-9]{3} —` дал 13 записей, соответствующих итоговой таблице. Повторный `git status --short` в четырёх checkout пустой; их HEAD не изменены. `git check-ignore` подтвердил, что отчёт — ignored локальный artifact в root `audit-results`, не изменение исходников/test/config четырёх репозиториев.
|
||||
|
||||
## 10. Машинно-считываемая таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| TQ-001 | FIX | P2 | High | backend | test/tbank-local-reads.e2e-spec.ts | Late-publication case never starts the protected producer | S | TQ-003,TQ-013 |
|
||||
| TQ-002 | FIX | P2 | Medium | frontend | src/pages/stocks/ui/StocksPage/StocksPage.test.tsx;src/shared/api/resource-query.test.tsx | Late-result assertions lack full loser-completion barrier | S | TQ-001,TQ-011 |
|
||||
| TQ-003 | FIX | P1 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts;src/modules/investments/tbank-sync/tbank-sync.repository.ts;test/upcoming-events.e2e-spec.ts | Critical hourly lifecycle and reservation lack direct executable proof | M | TQ-001,TQ-005 |
|
||||
| TQ-004 | FIX | P2 | High | frontend | src/pages/account/ui/AccountPage/AccountPage.test.tsx;src/pages/account/ui/AccountPage/AccountPage.tsx | Portfolio oracle forbids recovery and misses source-metadata wiring | S | TQ-010 |
|
||||
| TQ-005 | FIX | P2 | High | backend | test/upcoming-events.e2e-spec.ts;src/modules/investments/portfolio/infrastructure/forecast.repository.ts | Range oracle rejects complete-empty fallback and misses new-window publication | M | TQ-003,TQ-006,TQ-013 |
|
||||
| TQ-006 | INVESTIGATE | P2 | High | backend | src/integrations/moex/moex.client.spec.ts;test/upcoming-events.e2e-spec.ts | Missing source block semantics conflict with behavioral requirement | S | TQ-005,TQ-013 |
|
||||
| TQ-007 | FIX | P2 | High | backend,frontend | backend/src/common/dto-validation.spec.ts;backend/test/app.e2e-spec.ts;frontend/src/features/auth/ui/AuthForm/AuthForm.test.tsx | UTF-8 and token-role/cookie boundaries lack distinguishing cases | S | TQ-011 |
|
||||
| TQ-008 | FIX | P2 | High | backend | test/tbank-value-history.e2e-spec.ts | Publish-failure case fails upstream before transaction and omits snapshot assertion | S | TQ-001,TQ-012,TQ-013 |
|
||||
| TQ-009 | DELETE | P3 | High | backend | src/modules/investments/tbank-sync/tbank-screen.spec.ts | Orphan manual-command tests removable after owner confirms retirement | S | TQ-003 |
|
||||
| TQ-010 | SIMPLIFY | P2 | High | frontend | src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx;src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx | Old filter/reset oracles obstruct current read-only forecast and retry UX | M | TQ-004,TQ-005 |
|
||||
| TQ-011 | KEEP | P1 | High | backend,frontend | backend/test/app.e2e-spec.ts;backend/src/modules/auth/application/auth.service.spec.ts;frontend/src/shared/api/client.test.ts;frontend/src/features/auth/model/auth.test.tsx | Preserve real refresh CAS, usable winner and late-identity isolation | S | TQ-002,TQ-007 |
|
||||
| TQ-012 | KEEP | P1 | High | backend,frontend | backend/src/integrations/tbank/decimal.spec.ts;backend/src/modules/banking/bank-cards/domain/rub-money.spec.ts;backend/test/bank-cards.e2e-spec.ts;frontend/src/entities/bank-card/api/summary.test.ts | Preserve independent exact-money unit, persistence and sorting proofs | S | TQ-007,TQ-008,TQ-013 |
|
||||
| TQ-013 | KEEP | P1 | High | backend | test/tbank-operations-persistence.e2e-spec.ts;test/upcoming-events.e2e-spec.ts;test/bank-cards.e2e-spec.ts | Preserve deterministic rollback, dependency CAS and replay invariants | S | TQ-001,TQ-005,TQ-006,TQ-008,TQ-012 |
|
||||
@@ -0,0 +1,440 @@
|
||||
# Аудит 09 — Test Architecture & Infrastructure
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
**Дата:** 2026-10-11. **Режим:** read-only аудит тестовой инфраструктуры; записан только этот отчёт в родительском workspace.
|
||||
|
||||
| Область | Путь относительно workspace | Точный HEAD | Состояние при начале аудита |
|
||||
| --- | --- | --- | --- |
|
||||
| Workspace | `.` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | Чистый, `release/0.2.1` |
|
||||
| Frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | Чистый, `codex/fix-stock-screener-race` |
|
||||
| Backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | Чистый, `codex/add-stock-screener` |
|
||||
| Specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | Чистый, `codex/add-stock-screener` |
|
||||
| Docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | Чистый, `codex/add-stock-screener` |
|
||||
|
||||
Абсолютный корень: `/Users/ksv741/Project/vibe/WealthLedger`. Прочитаны локальные `AGENTS.md`, engineering/testing conventions и относящиеся к трассировке architecture/data источники. Conventions использованы для установления задуманного устройства, а не как доказательство полезности каждого правила.
|
||||
|
||||
**Skill:** установленный `ponytail-audit` загружен через Skill tool и применён к сложности test support. Его complexity-pass отделён от проверки isolation/lifecycle. Исправления не применялись. `technical-audit-2026-10-11.md` прочитан как список прежних гипотез; выводы ниже проверены по текущему checkout.
|
||||
|
||||
### Выполненные проверки
|
||||
|
||||
- В каждом репозитории: `git rev-parse HEAD`, `git status --porcelain=v1 --branch`.
|
||||
- Статический inventory через Glob/Read/Grep и `rg`: runner configuration, imports test support, lifecycle hooks, mocks/timers, локальные factories, consumers fixtures и зависимости subprocess.
|
||||
- Backend, после загрузки существующего NVM и активации местной `.nvmrc`:
|
||||
|
||||
```sh
|
||||
source "/Users/ksv741/.nvm/nvm.sh" && nvm use --silent && npm test -- --runInBand --no-cache --runTestsByPath src/modules/banking/bank-cards/domain/rub-money.spec.ts
|
||||
```
|
||||
|
||||
**PASS: 1 suite, 22 tests; Jest сообщил 0.379 s.** Это время одного локального запуска pure unit suite, не оценка CI или всего набора. Проверка подтверждает работоспособность дешёвой unit boundary без Nest bootstrap, SQLite и внешней сети. Coverage и Jest cache не создавались.
|
||||
- Первая попытка той же команды остановилась на `nvm: command not found`: NVM не был загружен в tool shell, сам тест не запускался. После `source` проверка прошла. Это ограничение оболочки аудита, не finding проекта.
|
||||
- Одна поисковая команда сначала включала отсутствующий frontend-каталог `test/`; inventory затем выполнен по фактическим `src/test/`, `src/`, `packages/`. Данных эта ошибка не меняла.
|
||||
|
||||
**Не запускались:** install, build, codegen, миграции, full-suite, coverage suite, `verify`, SQLite/HTTP suites, browser tests, performance/durability/container/regression runners. Реальные банковские и брокерские API не вызывались. Изменения исходников, тестов, конфигурации, lockfiles и Git history отсутствуют.
|
||||
|
||||
**Ограничение доказательств:** дефекты организации и возможные failure modes выведены из исходников. Частота flaky-сбоев, время bootstrap/migrations и экономия CI **не измерены**. Не воспроизведённые runtime-сценарии помечены отдельно; зелёный unit test не подтверждает исправность HTTP/SQLite инфраструктуры.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Основа достаточно проста и оправданна.** Backend разделяет colocated Jest unit, реальные SQLite suites и configured HTTP; frontend использует небольшие query/resource helpers, RTL/MSW и два browser layout-файла. Причин создавать общий test framework, builders DSL или cross-repo runtime не найдено.
|
||||
2. **Подтверждена межтестовая зависимость банковской suite — TI-001, P2.** Данные одного owner живут между `it`; restart-case проверяет переименование категории, сделанное другим case. Это непосредственно мешает изолированному запуску и перестановке сценариев.
|
||||
3. **Подтверждён неконтролируемый фоновой lifecycle — TI-002, P2.** В 10 функциональных HTTP suites зарегистрирован настоящий минутный scheduler. В части из них есть активные connection fixtures; длительный запуск способен начать работу вне Arrange/Act. Отдельный benchmark тоже запускает scheduler, что мешает изолированному измерению read budget.
|
||||
4. **Синхронизация фоновых сценариев местами привязана к скорости машины — TI-003, P2.** Sleeps на 5–10 ms используются как признак начала работы; для portfolio unit tests уже существует `work.done`, но вместо него опрашиваются mock calls. Это подтверждённый механизм риска, а не измеренная flaky-rate.
|
||||
5. **Targeted SQLite path допускает смешение source и build — TI-004, P2.** Crash producer загружается из `dist`, основной driver — через ts-jest из `src`; прямой runner не собирает build. Штатный `test:e2e` защищён предварительным build. Фактическое устаревание текущего `dist` — **UNVERIFIED**.
|
||||
6. **Cleanup небольшой и необязательный.** Opt-in regression самого runner выполняет пять полных функциональных проходов — TI-005, P3; в bond setup есть один option без consumer — TI-006, P3. Ни одна находка не оправдывает массовую перестройку тестов.
|
||||
|
||||
Всего: **6 findings — 4 P2 и 2 P3. P0/P1 не установлены.** Самые важные изменения уменьшают скрытое состояние и делают выполнение управляемым, сохраняя реальные DB/HTTP guarantees.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Фактический стек и размеры области
|
||||
|
||||
| Объект | Результат | Метод и граница подсчёта |
|
||||
| --- | --- | --- |
|
||||
| Frontend test files | 63 | `rg --files src packages -g '*.test.ts' -g '*.test.tsx' \| wc -l`; файлы, не expanded test cases |
|
||||
| Frontend browser files | 2 | По найденным `*.browser.test.tsx`: `EntryCard` и `position-layout`; оба запускаются в двух viewport instances |
|
||||
| Frontend JSDOM files | 61 | 63 минус 2 browser-файла; разграничение подтверждено `vitest.config.ts:26–65` |
|
||||
| Consumers `@/test/query` | 27 test files | `rg -l 'from "@/test/query"' src packages` с теми же test globs; прямые imports, не число вызовов |
|
||||
| Consumers `@/test/resource` | 20 test files | Аналогичный подсчёт прямых imports; дополнительно helper использует `src/test/handlers.ts:3` |
|
||||
| Consumers `@/test/server` | 27 test files | Аналогичный подсчёт прямых imports; lifecycle setup рассматривается отдельно |
|
||||
| Backend unit files | 39 | `rg --files src -g '*.spec.ts' \| wc -l` |
|
||||
| Backend e2e/integration files | 20 | `rg --files test -g '*.e2e-spec.ts' \| wc -l` |
|
||||
| Функциональные suites в `--all` | 19 | 20 файлов минус явно исключённый performance-file, `test/sqlite.cjs:7–17` |
|
||||
| Suites с `AppModule` | 14, из них 13 функциональных | `rg -l 'imports: \[AppModule\]' test -g '*.e2e-spec.ts' \| wc -l`; просмотр multiline imports и consumer каждого результата |
|
||||
| Overrides hourly scheduler | 3 suites | `portfolio-structure`, `tbank-value-history`, `upcoming-events`; остальные 10 функциональных AppModule suites запускают lifecycle настоящего provider |
|
||||
| Backend локальные `deferred<T>` | 4 определения | По `function deferred` в `test/`: local-reads, operations-sync, metadata-persistence, catalog-discovery |
|
||||
|
||||
Frontend manifest: Vitest `5.0.0`, RTL `16.3.3`, MSW `2.15.0`, Playwright `1.63.0`, TanStack Query `5.102.8`, JSDOM `30.0.1` (`frontend/package.json:64–116`). Backend: Jest `30.5.1`, ts-jest `29.4.12`, Nest testing `12.0.1`, Supertest `7.2.2`, Prisma `7.10.0`, better-sqlite3 `13.0.3` (`backend/package.json:40–105`). Это manifest versions; переустановка зависимостей не выполнялась.
|
||||
|
||||
### 3.2. Test Support Inventory
|
||||
|
||||
| Support / owner | Consumer / выполняемый поток | Назначение и оценка |
|
||||
| --- | --- | --- |
|
||||
| `frontend/vitest.config.ts:26–65` | Unit project; browser project | Разные environments и взаимоисключающие globs. Browser geometry не дублируется в JSDOM — **KEEP** |
|
||||
| `frontend/src/test/setup.ts:1–48` | Каждый JSDOM test file | jest-dom, i18n, strict MSW listener, RTL cleanup, auth reset, handler reset, browser API shims — **KEEP** |
|
||||
| `frontend/src/test/server.ts:1–7`, `handlers.ts:10–82` | Setup и 27 напрямую импортирующих suites | Один process-local MSW server и synthetic defaults. Нет browser worker или backend dependency — **KEEP** |
|
||||
| `frontend/src/test/query.tsx:5–24` | 27 files | Fresh QueryClient, query/mutation retries off, wrapper сохраняет тот же client при rerender — **KEEP** |
|
||||
| `frontend/src/test/resource.ts:1–27` | 20 files + handlers | Resource envelope, typed Meta и explicit overrides. Не ходит в сеть, не нормализует деньги — **KEEP** |
|
||||
| `frontend/src/pages/account/model/positions.fixtures.ts:1–73` | `positions.test.ts:6`, `position-groups.test.tsx:7`, `AccountPage.vm.test.tsx:14` | Три прямых consumers; typed data с null, mixed currencies и negative quantity — **KEEP** |
|
||||
| Page-local `renderPage` / `setup` | Account, Card, OperationForm, Stock, Bond suites | Явная provider composition, локальные handlers/controls. Практически один suite-local уровень orchestration; не общий DSL — **KEEP**, TI-006 для одного flag |
|
||||
| Auth test wrapper `features/auth/model/auth.test.tsx:20–28` | AuthProvider suite | QueryClientProvider + настоящий AuthProvider. Сценарии изменения identity живут у auth owner — **KEEP** |
|
||||
| `frontend/src/test/storybook.test.tsx:19–57` | Composed stories из явно выбранного набора | Проверяет isolation от network/storage/cookies, а не работу factory как таковой — **KEEP** |
|
||||
| Browser-local `afterEach(cleanup)` | Два browser-файла | Browser project не подключает JSDOM setup. Их cleanup обязателен, не дублирование global hook — **KEEP** |
|
||||
| `backend/package.json:123–139` | `npm test`, watch/cov/debug | Unit Jest: `rootDir=src`, colocated specs, node environment — **KEEP** |
|
||||
| `backend/test/jest-e2e.json:1–13` | SQLite runner → Jest | E2E config, isolated-env gate, ts-jest, `.js` import mapper — **KEEP** |
|
||||
| `backend/test/sqlite.cjs:6–95` | `test:e2e`, `test:sqlite`, OpenAPI/performance entries | Suite selection, свежий temp DB, штатные migrations, отдельный Jest process, signals и cleanup — **KEEP**; TI-004 для build prerequisite |
|
||||
| `backend/test/e2e-env.cjs:1–17` | Каждая e2e suite и crash subprocess | Проверка runner-owned URL/temp path до DB work; synthetic JWT/encryption config — **KEEP** |
|
||||
| Local DB constructors | Пять service/repository suites: operations-persistence, operations-sync, metadata, catalog, candles | Реальная SQLite + конкретный service без Nest app и HTTP. Это уже реализованный меньший scope — **KEEP** |
|
||||
| Suite-local DB seeds | Connections, memberships, portfolios, operations/coverage | Seed responsibility локальна; current revision, token и financial inputs доступны тесту. Глобального seed framework нет — **KEEP** |
|
||||
| Local poll helpers | `state`, `settled`, `requestPortfolio`, `load` | Разные смыслы: конкретный work state, dependency group, await existing handle. Нельзя объединять исключительно по похожему loop; TI-003 касается readiness sleeps |
|
||||
| `backend/test/fixtures/tbank-sync-crash.cjs:1–86` | `tbank-operations-sync.e2e-spec.ts:424–499` | Child process + IPC checkpoint + SIGKILL на durable boundaries. Обычный mock не заменяет crash — **KEEP**, TI-004 |
|
||||
| `backend/test/sqlite-regression.cjs:20–96` | Явный opt-in command в `README.md:250–254` | Уникальные DB, внешний sentinel, migration failure, SIGINT/SIGTERM cleanup — **KEEP protections / SIMPLIFY workload**, TI-005 |
|
||||
| `backend/test/bank-cards-durability.cjs:10–23,120–215` | `test:durability -- <baseline>` | Независимая historical build, migration prefix, restart, rollback, backup/restore. Explicit external artifact, не shared helper dependency — **KEEP** |
|
||||
| `backend/test/container-smoke.cjs:19–73`, `runtime-check.cjs:1–8` | Container test / build-runtime проверка | Packaging/native modules/proto/read-only image boundaries; не дополнительный business test framework — **KEEP** |
|
||||
| `backend/test/tbank-read-baseline.e2e-spec.ts` | Отдельный performance command | Большой synthetic dataset и read-budget measurement, исключён из `--all` — **KEEP separation**, TI-002 для scheduler |
|
||||
| `backend/test/fixtures/tbank-read-baseline.json` | Benchmark, `tbank-read-baseline.e2e-spec.ts:522–529` | Historical RPC comparison input; не текущий CI timing — **KEEP** |
|
||||
| `backend/test/fixtures/tbank-read-persistent.json:1–20` | Runtime consumer в просмотренных test/config/docs roots не найден | Historical measurement evidence, не executable factory. **KEEP как evidence**; отсутствие import не доказывает ненужность исторического результата |
|
||||
|
||||
Custom builders/classes, собственные matchers/`expect.extend`, snapshot serializers и concurrent-case режим в просмотренной test architecture не найдены. Policy/tooling tests `test/eslint-config.cjs`, `scripts/test-eslint.mjs`, `scripts/test-pre-commit.mjs` отмечены в inventory, но их содержательная целесообразность относится к аудитам 10/11.
|
||||
|
||||
### 3.3. Representative cases и границы чтения
|
||||
|
||||
Карта имён/config/support охватывает все найденные test files. Углублённая выборка risk-based, а не построчное чтение всех assertions.
|
||||
|
||||
| Выборка | Реально рассмотренные файлы / участок |
|
||||
| --- | --- |
|
||||
| 5 сложных frontend suites | `AccountPage.test.tsx` — оба render helpers, fixtures, state/route setup; `OperationForm.test.tsx` — весь файл; `CardPage.test.tsx` — setup, pending-page/spy teardown и consumers options; `StockPage.test.tsx`, `BondPage.test.tsx` — options, handlers, controls и все consumers |
|
||||
| Дополнительные RTL/MSW flows | `StocksPage.test.tsx`, `resource-query.test.tsx`, `client.test.ts`, `auth.test.tsx`, `AccountPage.vm.test.tsx` — async barriers, retry/time policy и wrapper ownership |
|
||||
| 5 простых frontend suites | `AuthForm.test.tsx`, `calendar.test.ts`, `period.test.ts`, `SourceSyncStatus.test.tsx`, `EntryCard.browser.test.tsx`; дополнительно `position-layout.browser.test.tsx` |
|
||||
| 5 сложных backend suites | `bank-cards.e2e-spec.ts` — helper closure, seeds, global counts, category dependency, restart; `upcoming-events.e2e-spec.ts` — bootstrap, cleanup, dependency polling, revision-aware seed; `tbank-operations-sync.e2e-spec.ts` — весь файл; metadata/catalog persistence — construction, hooks, producer/observer races и deferred closure |
|
||||
| 5 простых backend suites | `rub-money.spec.ts`, `bank-card.calculations.spec.ts`, `decimal.spec.ts`, `all-accounts.e2e-spec.ts`, `stocks.e2e-spec.ts` — unit/HTTP boundary и setup scope |
|
||||
| Дополнительный backend infrastructure | `app.e2e-spec.ts`, `openapi-contract.e2e-spec.ts`, `portfolio.service.spec.ts`, candle/local-reads/connection/history bootstrap, все собственные test runners и crash fixture |
|
||||
| Public intent / supporting docs | Местные testing conventions/README; `specs/.../frontend-ui-catalog/spec.md:40–88,97–105`; `docs/architecture/cross-cutting/data-freshness.md:63–83` |
|
||||
|
||||
**Вне scope:** правильность каждого assertion, полнота продуктовых regressions, общий coverage score и legacy сценарии как таковые — аудит 08; CI triggers — 11; production SRP — 01/06; non-test scripts — 10. Не исследовались реальные внешние API, performance profile всего приложения и архивная OpenSpec история.
|
||||
|
||||
### 3.4. Fixture Complexity Matrix
|
||||
|
||||
`Arrange` ниже — число явно видимых инфраструктурных операций в вызывающем тесте, не количество DB queries/framework internals. Глубина — последовательные project-owned helper calls; provider tree не считается цепочкой helper abstractions.
|
||||
|
||||
| Fixture/helper → consumers | Responsibility | Hidden side effects / сложность | Alternatives и решение |
|
||||
| --- | --- | --- | --- |
|
||||
| `createTestQueryClient` / `createQueryWrapper` → 27 files | Client lifecycle / wrapper | Один factory call; wrapper → factory — максимум два support calls. Fresh client, retries off; сетевых вызовов при construction нет | Сохранить; общий client вместо per-case создаст новую isolation проблему |
|
||||
| `resource(data, meta)` → 20 files + handlers | Envelope только | Один call; generic data, fixed resource key/date/revision, shallow Meta override | Сохранить. Для stale/failure/connection-sensitive scenarios metadata задавать явно; не применять runtime validator ко всем fixtures |
|
||||
| `positions.fixtures` → 3 files | Typed scenario data | Module-level objects, nested references при spread; consumers используют projections/overrides, общей мутации в рассмотренных путях не найдено | Сохранить; deep-freeze/clone framework без доказанной mutation не нужен |
|
||||
| `OperationForm.setup` → собственная suite | Auth + categories handler + render | Три инфраструктурных шага; fresh client внутри render, close spy возвращается. Data-only operation передаётся отдельно; assertions в setup нет | Сохранить локально: category endpoint не перекрывает тестовые operation handlers |
|
||||
| `CardPage.setup` → собственная suite | Auth + handler bundle + render + request capture | Четыре options, каждый имеет consumer; mutable request arrays принадлежат одному invocation. 1 helper, без builder layers | Сохранить; новые options добавлять по реальному сценарию |
|
||||
| `StockPage.setup` → собственная suite | Несколько endpoint handlers + providers + failure controls | Девять options, examined consumers используют все; handlers содержат domain-specific payloads. Большой объём — данные/API sections, а не глубина DSL | Не объединять со BondPage: metric/rating/coupon contracts различаются |
|
||||
| `BondPage.setup` → собственная suite | Аналогичная page composition | Семь options; шесть используются, `failBond` не имеет consumer; controls локальны | Удалить только unused option — TI-006; общий instrument test framework не нужен |
|
||||
| Banking `createCard` → `auth` → `token`, `op`, `createOp` | Happy-path HTTP arrange / payload creation | Максимум три локальных helper calls; `createCard/createOp` делают HTTP/DB write и ожидают успешный status. Проверки ошибок используют `auth(...).expect(...)` напрямую | Сохранить короткие HTTP helpers: скрытая happy-path assertion здесь намеренная, negative path управляемый |
|
||||
| Banking module-level owner/DB → все `it` | Shared suite state | До первого case users seed, после каждого case cleanup нет; category state пересекает границы сценариев | FIX TI-001: case-owned data и explicit prerequisites |
|
||||
| Upcoming `portfolio` / `refresh` / `settled` → собственная suite | Seed / launch / await dependencies | Три разные обязанности уже разделены; `portfolio` явно повышает dataRevision и использует user-specific revision. `settled` включает quiet interval | KEEP разделение; не превращать в `givenPortfolioReadyAndAssert` DSL |
|
||||
| Metadata/catalog `createService`, `seedConnection`, `deferred`, `settled` → по одной suite | Composition / seed / RPC gate / completion | Четыре небольших помощника с разными ролями, не четыре обязательных layers; RPC readiness иногда заменён sleep | FIX TI-003 для sequencing. Небольшие duplicated constructors не требуют общего DI container |
|
||||
| Portfolio unit polling → два launch cases | Ожидание publish mock | Два одинаковых loop по десять `setImmediate`; тест игнорирует доступный `work.done` | Использовать existing completion promise — TI-003, без нового helper |
|
||||
| Crash subprocess → три checkpoint cases | Настоящая потеря процесса | Контроль RPC/repository checkpoint, отдельный `dist` runtime, IPC, timeout и child cleanup | KEEP; согласовать freshness build — TI-004 |
|
||||
| Runner regression → 3 sequential + 2 parallel full runs и failure/signal cases | Isolation/lifecycle infrastructure | Полный product набор является payload каждого успешного infrastructure check | SIMPLIFY TI-005: маленький existing workload с нужными suite/process boundaries |
|
||||
|
||||
### 3.5. Проверка SRP / DRY / KISS / YAGNI / ISP / DIP
|
||||
|
||||
- **SRP:** в изученной выборке нет общего helper, который одновременно скрыто генерирует финансовый набор, мутирует DB, делает HTTP и проверяет весь business outcome. Banking happy-path helpers объединяют HTTP action и success status; negative cases обходят их напрямую, поэтому разбиение ради SRP увеличило бы ceremony.
|
||||
- **DRY:** семантически одинаковые freshness/reset patterns важнее одинаковых object literals. Provider composition повторяется, но QueryClient factory уже общая. Backend auth/JWT seed wrappers короткие и локальные; общего runtime между frontend/backend нет.
|
||||
- **KISS:** максимальная установленная project-owned цепочка arrange в рассмотренных support paths — три коротких banking helper calls. Цепочек по 5–10 factory layers не найдено. Большие stock/bond setup — плоские bundles handlers, не builders framework.
|
||||
- **YAGNI:** подтверждён один unused option TI-006 и избыточный объём product workload в opt-in infrastructure regression TI-005. Оснований удалять основные factories, MSW server, crash helper или DB runner нет.
|
||||
- **ISP/DIP:** mocks в основном структурные, ограничены используемыми boundary methods; interfaces/fake hierarchies не вводятся. `as never` ослабляет compile-time проверку fake, но само по себе не доказывает runtime поломку и не оправдывает написание полного fake Prisma. DB guarantees проверяются на настоящей SQLite.
|
||||
- **Lifecycle:** frontend global hook очищает DOM/auth/handlers; fake timers обычно восстанавливаются локально. Backend per-suite fresh DB не равен per-case isolation — TI-001; настоящий background provider мешает deterministic setup — TI-002; producer gates требуют explicit readiness/release — TI-003.
|
||||
- **Financial defaults:** `resource` не округляет деньги; fixtures и сценарии явно показывают null, zero, negative/fractional quantities, UTF-8 names, разные revision/user, signed64 и >2^53. Доказательств, что builder скрывает финансовый corner case, нет. Fixed metadata не доказывает fresh/stale policy: такие tests должны задавать значимые поля явно.
|
||||
- **Heavy bootstrap:** чистые money/calendar/domain tests используют imports напрямую, без AppModule. Пять persistence suites уже создают concrete services без HTTP. Stocks/bonds HTTP contracts стартуют весь AppModule; меньшая composition возможна, но сохранение настоящих guard/pipe/filter/route registration важнее немедленного сокращения setup. Это **INVESTIGATE**, не обязательный rewrite.
|
||||
- **Factory-internal tests:** отдельные self-tests `resource`, `createQueryWrapper`, builders не найдены. Storybook isolation и SQLite runner regression имеют реальные границы защиты: первый запрещает network/storage, второй защищает пользовательскую БД и child-process cleanup.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### TI-001 — Банковская suite использует состояние предыдущих test cases
|
||||
|
||||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = M**
|
||||
|
||||
**Evidence:** `backend/test/bank-cards.e2e-spec.ts:23–39,77–105,106–145,371–390,710–753`; consumers: все `it` в `describe('Manual bank cards configured HTTP and real SQLite')`.
|
||||
|
||||
**Claim.** Per-suite fresh SQLite не обеспечивает самостоятельность банковских scenarios: часть cases требует данных/изменений, выполненных предыдущими `it`.
|
||||
|
||||
**Current mechanism.** Один module-level owner и stranger создаются в `beforeAll`; app/DB повторно используются, `beforeEach/afterEach` cleanup отсутствует. Case `renames default category...` меняет default category на «Остальное» (`371–386`). Case `preserves history... across app restart` сам создаёт карту/операции, но после restart ожидает ту же строку «Остальное» (`728–730`), не выполняя переименование. Case rename также читает default category до собственного `createCard` (`372–373`); её появление зависит от прежней записи. Constraint-case получает существующую category (`743`).
|
||||
|
||||
**Impact / actual cost.** Порядок тестов становится скрытым prerequisite. Targeted run одного rename/restart/constraint case на новой БД не воспроизводит исходные условия полной suite. Добавление нового раннего case меняет shared category/card state. **Impact unmeasured:** стоимость диагностики и частота падений не измерялись; runtime isolated run в этом аудите не выполнялся.
|
||||
|
||||
**Why now.** Дешёвые целевые проверки нужны для финансовых изменений; текущая организационная зависимость лишает их надёжности независимо от корректности business assertions.
|
||||
|
||||
**Minimum change.** Каждый независимый case создаёт owner/stranger или свой явно очищенный набор с FK-aware cleanup. Rename и constraint cases сами создают prerequisite card/categories. Restart-case сам переименовывает category до restart. App можно оставить suite-scoped; не нужна отдельная БД или migration на каждый `it`.
|
||||
|
||||
**Guarantees at risk.** Не удалять реальную SQLite, concurrent writes, replay fingerprints/tombstones, archive/version checks и restart внутри одного case. Смена owner требует обновить auth helpers, а cleanup — соблюдать FK order.
|
||||
|
||||
**Counterarguments / trade-offs.** Shared DB снижает bootstrap cost, случайные card IDs изолируют многие записи, некоторые global assertions сравнивают before/after вместо абсолютного значения. Это хорошие локальные меры, но они не устраняют найденную category dependency. Намеренный sequential journey допустим внутри одного `it`; здесь scenarios оформлены как независимые cases.
|
||||
|
||||
**Verification or rollback.** На disposable SQLite выполнить rename и restart отдельно через Jest `--testNamePattern`, а затем полную банковскую suite. Для filtered запуска сохранить runner-owned env: обычный runner сейчас не пересылает произвольные Jest flags. Проверить отсутствие зависимости от чужого case, затем real concurrency/replay outcomes. Rollback — вернуть только изменение test setup; production/schema не затрагивать.
|
||||
|
||||
**Related finding IDs:** TI-002, TI-003.
|
||||
|
||||
### TI-002 — Настоящий scheduler запускается в suites, не управляющих его работой
|
||||
|
||||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = S**
|
||||
|
||||
**Evidence:**
|
||||
|
||||
- Composition/lifecycle: `backend/src/app.module.ts:18–36`, `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:13–25`, `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:19–21,48–86,119–175`.
|
||||
- Consumers: `backend/test/all-accounts.e2e-spec.ts:30–61`, `backend/test/tbank-connection.e2e-spec.ts:30–54`, `backend/test/tbank-local-reads.e2e-spec.ts:100–141`, `backend/test/tbank-income-local.e2e-spec.ts:58–100`, `backend/test/tbank-ledger-local.e2e-spec.ts:44–78`.
|
||||
- Benchmark: `backend/test/tbank-read-baseline.e2e-spec.ts:199–247,355–357,410–485`.
|
||||
- Контрпримеры: `backend/test/upcoming-events.e2e-spec.ts:140–152`, `backend/test/portfolio-structure.e2e-spec.ts:40–49`, `backend/test/tbank-value-history.e2e-spec.ts:47–56`.
|
||||
|
||||
**Claim.** Тестовый app lifecycle не отделён от настоящего фонового scheduler в части HTTP/read suites и benchmark.
|
||||
|
||||
**Current mechanism.** `AppModule` включает `TbankSyncModule`, provider создаёт `setInterval(..., 60_000)` на `app.init/listen`. `tick` читает connections, резервирует hourly work и запускает membership/portfolio/operations/events. Из 13 функциональных AppModule suites только три override scheduler; в остальных 10 таймер активен. Пять перечисленных connection-oriented functional suites создают connection fixtures. Benchmark держит app во время большой DB population, допускает длительный setup/test и собирает upstream counters.
|
||||
|
||||
**Impact / actual cost.** При достижении минутного срока данные/metadata и counters могут меняться вне тестового Act; возможны неожиданные upstream mock calls и новые work rows. В benchmark фоновые writes/refresh конкурируют с измеряемыми reads. **Impact unmeasured:** длительность текущих suites, факт срабатывания tick и flaky frequency не измерены. Фактический внешний сетевой запрос не установлен.
|
||||
|
||||
**Why now.** Isolation не должна зависеть от того, уложился ли runner в минуту. Особенно это важно для утверждений «GET не запускает upstream» и измерения read-only budget.
|
||||
|
||||
**Minimum change.** В не-scheduler suites применить уже имеющийся `.overrideProvider(TbankHourlyScheduler).useValue(...)` без lifecycle таймера. Benchmark также отключает scheduler и явно запускает только необходимую подготовку. Для scheduler behavior нужен отдельный управляемый time/lifecycle case; не добавлять production env flag ради tests.
|
||||
|
||||
**Guarantees at risk.** Сохранить настоящие controllers/guards/pipes, migrations, DB publication fences и ручной запуск source owners в relevant scenarios. Scheduler tests должны сохранять реальный `tick`/reservation behavior; override не должен стать способом скрыть их ошибки.
|
||||
|
||||
**Counterarguments / trade-offs.** Большинство функциональных suites, вероятно, заканчивается раньше первого tick; auth/cards/stocks/bonds/OpenAPI обычно не создают connections, поэтому там интервал может быть inert. `app.close` очищает interval. Эти гарантии уменьшают вероятность, но не делают connection suites deterministic; cleanup timer не отменяет уже запущенную работу. Три существующих overrides показывают, что меньшая boundary уже принята.
|
||||
|
||||
**Verification or rollback.** После override отдельно проверить один local-read HTTP case и benchmark setup с временем за минутной границей: новых hourly work/upstream calls быть не должно. Затем relevant functional suites. Проверки самого scheduler — с controlled clock. Rollback — только provider override; production scheduler не меняется.
|
||||
|
||||
**Related finding IDs:** TI-001, TI-003, TI-005.
|
||||
|
||||
### TI-003 — Readiness фоновой работы зависит от sleeps и polling вместо доступного completion handle
|
||||
|
||||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = M**
|
||||
|
||||
**Evidence:**
|
||||
|
||||
- Consumers readiness sleeps: `backend/test/tbank-metadata-persistence.e2e-spec.ts:130–178`, `backend/test/tbank-catalog-discovery.e2e-spec.ts:179–225`, `backend/test/tbank-operations-sync.e2e-spec.ts:152–181,183–210,523–550`.
|
||||
- Async launch mechanism: `backend/src/integrations/tbank/instrument-metadata.service.ts:75–108,111–129`, `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:157–185,233–275`.
|
||||
- Consumers polling и существующий handle: `backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:100–110,150–154`, `backend/src/modules/investments/portfolio/application/portfolio.service.ts:234–306`.
|
||||
- Контрпримеры: `backend/test/tbank-local-reads.e2e-spec.ts:69–77`, `backend/test/portfolio-structure.e2e-spec.ts:81–100`.
|
||||
|
||||
**Claim.** Некоторые Arrange/Act sequences не имеют явного сигнала «producer дошёл до управляемого RPC», а часть tests не ждёт уже существующую completion promise.
|
||||
|
||||
**Current mechanism.** Metadata/catalog/operations services после reservation запускают async drain через `setImmediate` и затем выполняют DB/access awaits. Tests ждут `delay(5)` или `delay(10)` и считают producer начавшимся; после этого меняют connection или присоединяют другую работу. Два portfolio unit cases опрашивают `repository.publish.mock.calls` десять event-loop turns, хотя `service.request` возвращает `{id, done}`. В metadata coalescing-case assertions стоят перед `blocked.resolve`; при failure до release нет `finally`, который освободил бы producer.
|
||||
|
||||
**Impact / actual cost.** Sequencing зависит от CPU/SQLite scheduling. На медленном запуске возможен другой interleaving или падение mock call assertion. При раннем failure gate может остаться открытым до teardown и затруднить диагностику. Polling publish не выражает полный lifecycle задачи. **Impact unmeasured:** нет воспроизведённого flaky-run и оценки времени; речь о доказанном способе координации.
|
||||
|
||||
**Why now.** Race/connection scenarios должны контролировать точки начала/завершения, сохраняя реальный repository/CAS путь. Увеличение sleep не устанавливает ordering guarantee.
|
||||
|
||||
**Minimum change.** Mock upstream отмечает `started` и блокируется на существующем gate; тест ждёт этот сигнал перед replace/join. Release помещается в `finally`, когда case может выйти раньше. Где есть `work.done`, await именно его. Где completion представлен только durable work row, оставить bounded state polling и meaningful error; не переводить SQLite целиком на fake timers и не создавать универсальную race DSL.
|
||||
|
||||
**Guarantees at risk.** Барьер должен стоять на внешнем RPC или явной publication boundary, не заменять transaction/repository fake. Не сериализовать сами конкурирующие операции, не удалять проверки stale revision/attempt и final state.
|
||||
|
||||
**Counterarguments / trade-offs.** Настоящая SQLite может делать короткий polling самым простым reliable completion способом. Awaited `service.read` иногда даёт producer время на запуск; на обычной машине sleep может работать стабильно. Поэтому не удаляются все `delay` и `settled`: distinction между state polling и readiness sleep принципиален. Existing `work.done` consumers подтверждают более простой путь для portfolio без нового API.
|
||||
|
||||
**Verification or rollback.** Запустить только затронутые cases в среде с локальными портами и disposable DB. Продемонстрировать, что replace/join выполняется после `started`, а case заканчивается после release/completion; проверить failure-path cleanup. Для portfolio unit достаточно двух existing launch cases. Повторять тесты для оценки flaky-rate только при оставшемся сомнении, не заменять correctness многократным зелёным запуском.
|
||||
|
||||
**Related finding IDs:** TI-001, TI-002, TI-004.
|
||||
|
||||
### TI-004 — Targeted runner не задаёт freshness prerequisite для crash subprocess из `dist`
|
||||
|
||||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = S**
|
||||
|
||||
**Evidence:** `backend/test/sqlite.cjs:7–19,71–85`; `backend/test/jest-e2e.json:7–12`; `backend/test/tbank-operations-sync.e2e-spec.ts:424–499`; `backend/test/fixtures/tbank-sync-crash.cjs:1–17,59–82`; `backend/package.json:28–34`; `backend/README.md:250–254`.
|
||||
|
||||
**Claim.** Допустимый targeted launch может проверять исходники основного driver и другой build crash producer одновременно.
|
||||
|
||||
**Current mechanism.** Runner принимает любой existing e2e filename и запускает ts-jest. Operations-sync suite fork-ит fixture, которая требует services/repository/Prisma из `dist`. Прямой `node test/sqlite.cjs tbank-operations-sync.e2e-spec.ts` и передача такого filename в `test:sqlite` не выполняют build. `test:e2e`, performance и OpenAPI manifest commands предварительно собирают приложение.
|
||||
|
||||
**Impact / actual cost.** Если `dist` отсутствует, child не достигает checkpoint; если build старый, parent и producer могут иметь разную reservation/publication семантику. Возможны ложные failure или недостоверная green proof относительно текущего source. **UNVERIFIED:** текущий `dist` не проверялся на freshness; доказан разрешённый launch path без prerequisite, а не наличие устаревшего build сегодня. **Impact unmeasured.**
|
||||
|
||||
**Why now.** Самый дешёвый targeted SQLite path должен честно сообщать свои inputs; именно crash tests используют иной runtime artifact.
|
||||
|
||||
**Minimum change.** В existing README/test command guidance явно связать dist-dependent targeted suite с `npm run build && node test/sqlite.cjs tbank-operations-sync.e2e-spec.ts`. У runner сделать понятной build prerequisite для этой suite, если пользователи продолжают запускать её напрямую. Не добавлять checksum framework и не делать build обязательным для всех pure/DB-only tests; наличие файла само по себе не доказывает freshness.
|
||||
|
||||
**Guarantees at risk.** Сохранить реальный отдельный child process, SIGKILL, checkpoint semantics, migrated temporary DB и сравнение durable rows после recovery. Замена crash helper на mocked restart уменьшит доказательность.
|
||||
|
||||
**Counterarguments / trade-offs.** Штатный full e2e flow уже безопаснее: build выполняется перед всеми suites. Default `test:sqlite` выбирает только auth/cards и не использует crash fixture. Проблема локальна targeted/custom invocation. Повторный build стоит времени; достаточно explicit prerequisite, без усложнения общей схемы запусков.
|
||||
|
||||
**Verification or rollback.** Проверить две existing entry boundaries: targeted suite без build даёт понятное ограничение; после fresh build её три crash checkpoints работают с текущими services. Эта runtime проверка отложена, потому что требует build/migrations. Rollback — отменить только guidance/preflight change, не child protocol.
|
||||
|
||||
**Related finding IDs:** TI-003, TI-005.
|
||||
|
||||
### TI-005 — Opt-in regression runner использует слишком большой product workload
|
||||
|
||||
**Decision = SIMPLIFY · Severity = P3 · Confidence = High · Effort = M**
|
||||
|
||||
**Evidence:** `backend/test/sqlite-regression.cjs:20–76,77–91`; `backend/test/sqlite.cjs:7–17,58–89`; consumer: `backend/README.md:250–254`.
|
||||
|
||||
**Claim.** Regression isolation/cleanup самого runner повторяет полный functional product набор там, где для механизма достаточно меньшего существующего workload.
|
||||
|
||||
**Current mechanism.** Каждый `check` вызывает `test/sqlite.cjs --all`; три последовательных и два параллельных normal passes проверяют allocated directories, uniqueness, cleanup и неизменность sentinel. Затем отдельно запускаются migration failure и signals. При прохождении всех normal checks текущий workload — **5 × 19 = 95 functional suite invocations**, каждая со свежими migrations/Jest; failure/signal attempts в число 95 не включены.
|
||||
|
||||
**Impact / actual cost.** Maintenance/check cost связан со всеми business suites; чужая продуктовая регрессия может остановить проверку runner mechanics. Количество повторных suite invocations установлено статически. **Impact unmeasured:** wall time, CPU и CI minutes не измерялись; command opt-in, не утверждается его запуск на каждом PR.
|
||||
|
||||
**Why now.** Механизмы защиты DB и cleanup стоит проверять достаточно дешёво, чтобы ими действительно пользовались после изменения runner.
|
||||
|
||||
**Minimum change.** Использовать существующие небольшие SQLite suites как workload: один normal проход должен сохранить переход между минимум двумя suites; parallel checks должны сохранить минимум два одновременных runner process. Failure/signal/sentinel assertions оставить. Full product e2e уже имеет собственный entrypoint. Новая искусственная suite или новый test framework не требуются.
|
||||
|
||||
**Guarantees at risk.** Уникальность DB между suites и процессами, штатные migrations, реальная child failure, неизменность sentinel, SIGINT/SIGTERM nonzero exit и cleanup. Нельзя сократить workload до пустого mocked subprocess, который перестаёт обращаться к SQLite.
|
||||
|
||||
**Counterarguments / trade-offs.** Полные проходы дополнительно проверяют все suites на внешнее shared state и дают broad stress evidence. Поэтому сокращение не срочное; имеет смысл после маленького доказательства эквивалентности runner guarantees. Three identical sequential passes сами по себе не доказывают, что пять полных product проходов — минимально нужный объём.
|
||||
|
||||
**Verification or rollback.** Сопоставить assertions старого и малого workload: минимум две suite directories, два parallel runners, sentinel и failure/signal cleanup. Один измеренный запуск старого/нового workload даст baseline для экономии; пока не заявлять ускорение. Rollback — вернуть workload selection в opt-in runner.
|
||||
|
||||
**Related finding IDs:** TI-002, TI-004.
|
||||
|
||||
### TI-006 — `failBond` — unused flexibility в локальном frontend setup
|
||||
|
||||
**Decision = DELETE · Severity = P3 · Confidence = High · Effort = S**
|
||||
|
||||
**Evidence:** `frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:88–107,294–404`; consumers `setup` — `296,337,345,353,364,371,383`.
|
||||
|
||||
**Claim.** Option `failBond` и его endpoint branch существуют без выполняющего их scenario.
|
||||
|
||||
**Current mechanism.** Local `setup` не экспортируется. `failBond` определяется в options и читается только внутри default bond handler; ни один callsite не передаёт его. Остальные options имеют реальные consumers. В соседней stock suite `failStock` используется в recovery case (`frontend/src/pages/stock/ui/StockPage/StockPage.test.tsx:473–481`), поэтому удалять симметрично оба flags нельзя.
|
||||
|
||||
**Impact / actual cost.** Небольшая лишняя ветвь чтения и сопровождения helper, создающая видимость поддерживаемого scenario. **Impact unmeasured;** это необязательный cleanup, не дефект продукта и не требование добавить тест ради покрытия ветви.
|
||||
|
||||
**Why now.** Только при ближайшей правке этой suite; отдельная migration ради одного flag не оправданна.
|
||||
|
||||
**Minimum change.** Удалить option и conditional response, оставить happy-path handler. Если появляется настоящий unavailable-bond scenario, case может явно добавить соответствующий MSW handler.
|
||||
|
||||
**Guarantees at risk.** Не удалять ambiguity handler, failure coupons/rating и recovery UI production logic; находка относится только к невызванной test-support ветви.
|
||||
|
||||
**Counterarguments / trade-offs.** Flag мог готовиться под будущий сценарий; это не current consumer. Если такой scenario уже нужен, owner должен добавить его по продуктовому риску — решение о assertions относится к аудиту 08.
|
||||
|
||||
**Verification or rollback.** Static dependency closure уже прослежена в полном файле. При реализации достаточно existing targeted BondPage suite и typecheck; не писать test на отсутствие option. Rollback — вернуть локальный flag при появлении consumer.
|
||||
|
||||
**Related finding IDs:** —.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Кандидат / механизм | Основание и граница |
|
||||
| --- | --- | --- |
|
||||
| FIX | Banking per-case state — TI-001 | Самостоятельные prerequisites, app может оставаться общим |
|
||||
| FIX | Hourly provider в не-scheduler suites — TI-002 | Existing override; scheduler behavior сохраняется в своей boundary |
|
||||
| FIX | Readiness/completion — TI-003 | Explicit RPC start/release и existing `done`; не увеличивать sleeps |
|
||||
| FIX | Dist-dependent targeted command — TI-004 | Согласовать build input; не менять все runner entrypoints |
|
||||
| SIMPLIFY | Workload runner regression — TI-005 | Малый real SQLite payload при сохранённых safety assertions |
|
||||
| DELETE | `failBond` test option — TI-006 | Нулевой consumer подтверждён; production behavior не удаляется |
|
||||
| KEEP | Real SQLite migrations + runner env gate + temp cleanup | `sqlite.cjs:60–89`, `e2e-env.cjs:1–13`; native constraint/transaction proof и защита пользовательской DB |
|
||||
| KEEP | Refresh CAS, financial concurrency и replay tests | `app.e2e-spec.ts:161–213`, `bank-cards.e2e-spec.ts:118–164,391–423,462–524,622–708`; mocks не равносильны реальной записи |
|
||||
| KEEP | Revision/attempt publication и crash process | `tbank-migration.e2e-spec.ts:150–168,194–231`, operations-sync crash cases; не заменять rollback/CAS обычными mocks |
|
||||
| KEEP | Pure money/calendar boundary | `rub-money.spec.ts`, `bank-card.calculations.spec.ts`, `decimal.spec.ts`; никаких app/DB dependencies ради вычислений |
|
||||
| KEEP | QueryClient factory + strict MSW + resource envelope | Shared technical policy уже имеет consumers; singleton client и cross-repo framework ухудшат isolation |
|
||||
| KEEP | Explicit page providers и domain fixtures | Повторение короткого arrange делает local expectations видимыми |
|
||||
| KEEP | Два browser viewport instances | Проверяют настоящую geometry, которой нет в JSDOM; это не suite duplication |
|
||||
| KEEP | Storybook network/storage isolation | Прямое требование synthetic catalog; не «тест factory ради factory» |
|
||||
| KEEP | Build/container/durability boundaries | Packaging/crash/backup risks не воспроизводятся pure assertions |
|
||||
| KEEP | Локальные `deferred` сейчас | Backend `target=ES2023` (`tsconfig.json:13`) не предоставляет Promise.withResolvers в default lib. Не копировать frontend ES2024 решение, меняя TS config ради четырёх коротких helpers |
|
||||
| INVESTIGATE | Узкий HTTP module для stocks/bonds | AppModule bootstrap/migrations time **not measured**; сначала сохранить реальные guards/pipes/filter/routes и проверить цену |
|
||||
| INVESTIGATE | Explicit QueryClient teardown для незавершённых requests | Fresh clients и RTL cleanup уже есть; утечка/мешающий GC timer **UNVERIFIED**. Не вводить client registry или `gcTime` policy без воспроизведения |
|
||||
|
||||
### Ponytail complexity-pass
|
||||
|
||||
- `shrink:` уменьшить product workload opt-in runner regression; использовать existing small SQLite suites с теми же lifecycle/sentinel checks. [`backend/test/sqlite-regression.cjs:20–91`, TI-005]
|
||||
- `shrink:` заменить два portfolio publish-poll loops на existing `work.done`. [`backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:100–110,150–154`, часть TI-003]
|
||||
- `delete:` убрать unused `failBond` option и conditional handler branch; replacement — обычный success handler. [`frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:88–107`, TI-006]
|
||||
|
||||
**net: -3 lines, -0 deps possible.** Это консервативный подсчёт только конкретного удаления `failBond`: одна option-строка плюс замена трёх строк conditional expression одной (`1 + (3 − 1)`). Patch не применялся; сокращение polling/runner LOC не оценивалось. Кандидатов на удаление dependency не установлено.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| Действие | Ожидаемый класс пользы | Что измерено | Обязательные regressions / safety boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| TI-001: case-owned data | Cognitive load, время targeted diagnosis, maintenance | Static dependency category rename → restart; время **not measured** | Money, FK order, concurrent refunds/refresh, replay/tombstones, restart |
|
||||
| TI-002: controlled scheduler | Reliability, reproducibility; чистота benchmark measurement | 10 functional AppModule suites без override; runtime влияние **not measured** | HTTP/DB остаются настоящими; scheduler проверяется управляемо |
|
||||
| TI-003: readiness / `done` | Flakiness risk, ясность Arrange/Act, меньше incidental polling | Concrete sleep/launch/completion paths; flaky frequency **not measured** | Не подменить CAS; awaited release и terminal state, failure cleanup |
|
||||
| TI-004: fresh build для crash path | Достоверность proof, diagnostic clarity | Source/dist split; actual stale artifact **UNVERIFIED** | Actual separate process/SIGKILL/current services/temp DB |
|
||||
| TI-005: меньший runner workload | Local verification time; CI minutes только если command используется в CI | 95 suite invocations в успешных normal checks; wall time **not measured** | Две suite boundaries, два parallel processes, sentinel, миграции, signals/failure |
|
||||
| TI-006: unused flag | Очень небольшая maintenance/cognitive экономия | Dependency closure, минимальный подсчёт удаления выше | Existing BondPage behavior; новые tests ради флага не нужны |
|
||||
|
||||
Локальная pure unit проверка заняла сообщённые Jest **0.379 s** 2026-10-11; её нельзя экстраполировать на HTTP/SQLite или приписывать предложенному cleanup.
|
||||
|
||||
Исторические benchmark JSON содержат application-read metrics и различный hardware/profile (`test/fixtures/tbank-read-baseline.json:1–19`, `tbank-read-persistent.json:1–20`). Это **не CI timing**; даты измерения в просмотренных headers не указаны. Их latency не использована для расчёта экономии инфраструктуры и не представлена как актуальное состояние.
|
||||
|
||||
Нет доказанных процентов ускорения, объёма agent-token экономии или необходимости убрать зависимости.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **TI-002:** применить existing scheduler override в connection/read HTTP suites и benchmark. Большая польза isolation при маленьком diff; real time остаётся только в scheduler-specific proof.
|
||||
2. **TI-001:** сделать rename/restart/constraint cases self-contained. Сначала добавить явные prerequisites, затем убрать shared owner state между независимыми cases. Полный финансовый suite остаётся необходимой regression boundary.
|
||||
3. **TI-003:** в двух portfolio unit launch cases ждать `work.done`. Уже существующий handle короче и точнее нового polling helper.
|
||||
4. **TI-004:** записать существующую build + targeted crash command как правильный путь. Понятное ограничение предпочтительнее проверки freshness через новый hash/manifest механизм.
|
||||
5. **TI-006:** удалить unused flag при ближайшей правке BondPage suite; самостоятельная cleanup-кампания не нужна.
|
||||
|
||||
### Решения с тестовым подтверждением
|
||||
|
||||
6. **TI-003:** убрать readiness sleeps в metadata/catalog/operations producer races; поставить gate на реальную upstream boundary, гарантировать release/settlement при выходе из case. Сохранить real SQLite.
|
||||
7. **TI-005:** сократить opt-in infrastructure workload после доказательства сохранности suite/process isolation, failure/signals и sentinel assertions. Измерить один before/after запуск; не объявлять неподтверждённое ускорение.
|
||||
|
||||
Новая ADR для этих локальных исправлений не нужна. ADR/отдельное решение потребуется только при смене базовой testing boundary, backend compiler lib ради новых APIs или принятии нового общего HTTP harness; таких изменений аудит не требует.
|
||||
|
||||
### Minimal Test Infrastructure Baseline
|
||||
|
||||
| Граница | Минимально достаточный baseline | KEEP / REMOVE / CONSOLIDATE |
|
||||
| --- | --- | --- |
|
||||
| Pure domain | Existing Jest/Vitest imports, explicit financial/calendar data | **KEEP**; не стартовать app/DB |
|
||||
| Service orchestration | Concrete service + узкие external mocks + управляемые gates | **KEEP** real completion handles; **REMOVE** readiness sleeps там, где нужен ordering |
|
||||
| Backend DB | Existing isolated SQLite runner, production migrations, case-owned state | **KEEP** per-suite DB и env gate; **CONSOLIDATE** launch guidance, не DB mocks |
|
||||
| Backend HTTP | ConfigureApp + настоящие guard/pipe/filter + controlled external boundaries | **KEEP**; **REMOVE** автоматический scheduler lifecycle из не-scheduler suites |
|
||||
| Crash/rollback/restore | Existing subprocess/baseline artifact/backup runners | **KEEP** независимые boundaries; **CONSOLIDATE** build prerequisite с explicit command |
|
||||
| Frontend API/UI | Fresh test client + strict MSW + explicit providers + RTL cleanup | **KEEP**; page fixtures остаются локальными |
|
||||
| Layout | Existing Vitest browser project на narrow/wide | **KEEP**; не имитировать dimensions в JSDOM |
|
||||
| Catalog | Synthetic stories + network/storage isolation check | **KEEP**; отдельная story-fixture DSL не нужна |
|
||||
| Runner regression | Small existing SQLite workload + sentinel/failure/signal assertions | **CONSOLIDATE** mechanics checks вокруг малого workload; full product e2e отдельно |
|
||||
| Cross-repo reuse | Публичные contract artifacts, без runtime imports helpers | **KEEP** автономность frontend/backend/specs/docs |
|
||||
|
||||
## 8. Duplication vs Readability Trade-off
|
||||
|
||||
| Повторение | Что совпадает семантически | Решение |
|
||||
| --- | --- | --- |
|
||||
| Query/Mantine/Router provider trees | Framework composition; маршруты/auth requirements различаются | Factory QueryClient уже общая. Не делать omnibus `renderWithEverything` с десятком switches |
|
||||
| Stock/Bond handlers | Похожая topology; financial metrics/rating/coupons, identifiers и outcomes различаются | Local ownership важнее визуального DRY. Удалить только доказанный dead option |
|
||||
| Backend JWT/connection seeds | Несколько строк config; revision/account/lifecycle цели различаются | Не делить между репозиториями и не строить users/builders framework |
|
||||
| `settled` loops | Metadata/catalog ждут отсутствие running work; operations ждёт конкретное state; forecast ждёт dependency group и quiet period | Не унифицировать все loops по синтаксису. При изменении owner сделать условия/диагностику явными |
|
||||
| Portfolio unit publish polling | Два одинаковых обхода вместо уже предоставленного completion | Сократить через `work.done`, не извлекать `waitForPublish` |
|
||||
| Banking happy-path helpers | HTTP/auth/expected success повторяются реально | Сохранить helpers; negative status assertions оставить в test body |
|
||||
| Четыре `deferred<T>` | Promise control похож; часть поддерживает reject | Цена мала, lib compatibility различается с frontend. Не менять compiler contract ради DRY; отдельно устранять sequencing недостатки |
|
||||
| Restart/crash/durability scenarios | Слова «restart» совпадают, failure boundaries разные | Nest provider restart, SIGKILL процесса и old/new-build rollback **не взаимозаменяемы** |
|
||||
| Runner regression full passes | Повторяются все product cases, а assertions проверяют runner directories/sentinel | Малый real workload может сохранить именно infrastructure proof — TI-005 |
|
||||
|
||||
## 9. Cross-audit signals
|
||||
|
||||
- **08:** соответствие named race исходному data flow и ожидание позднего response — `backend/test/tbank-local-reads.e2e-spec.ts:489–515`, `frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–237`; assertions здесь не переаудировались.
|
||||
- **08:** cache/error scenarios с изменением handler без следующего Act — `frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:363–369`, `src/pages/stock/ui/StockPage/StockPage.test.tsx:463–471`; вопрос содержательной regression proof.
|
||||
- **08/12:** coverage include захватывает `src/pages/account/model/positions.fixtures.ts`, exclude ограничен test/stories/support globs — `frontend/vitest.config.ts:15–24`; оценить смысл coverage input, не добавлять tests на fixture.
|
||||
- **06/13:** termination уже начавшейся hourly work при app shutdown — `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–54,84–86,197–218`; production lifecycle не расследовался сверх необходимого для TI-002.
|
||||
- **10/11:** policy-tooling tests и стоимость repeated package builds — frontend `package.json:18–26`, `scripts/test-eslint.mjs`; backend `test/eslint-config.cjs`; их обязательность/trigger budget вне этого аудита.
|
||||
|
||||
## 10. Open questions / verification plan
|
||||
|
||||
| Вопрос, реально меняющий решение | Текущее состояние | Самый дешёвый следующий шаг |
|
||||
| --- | --- | --- |
|
||||
| Должна banking suite быть sequential journey или набором независимых cases? | Формат `it` и отдельные titles указывают на самостоятельные cases; category dependency доказана | Запустить rename/restart отдельно на disposable DB. Если journey намеренный — держать dependent steps внутри одного scenario, а не скрывать dependency между `it` |
|
||||
| Срабатывает ли hourly tick на обычном runner до конца connection suites? | **UNVERIFIED**, registration/connection path доказан | Один controlled-clock/instrumented targeted run. Отсутствие срабатывания сегодня не отменяет полезность deterministic override |
|
||||
| Какие targeted SQLite команды реально используют разработчики для crash cases? | Full command собирает build; direct runner принимает dist-dependent suite | Проверить usage/guidance и выполнить один targeted launch после fresh build. Отдельная freshness registry не нужна |
|
||||
| Достаточен ли маленький workload для safety regression runner? | Product повторение доказано; эквивалентность ещё не проверена | Малый prototype на existing suites; сверить directory/process/sentinel/failure/signal assertions и один before/after timing |
|
||||
| Есть ли dangling frontend requests/GC timers после cleanup? | **UNVERIFIED**; fresh clients, auth generation fence и cleanup уменьшают риск | Только при воспроизведении pending-after-test симптома снять handles/requests для одной suite; затем локальный teardown. Не добавлять глобальный registry заранее |
|
||||
| Стоит ли уменьшать stocks/bonds AppModule bootstrap? | Framework/DB bootstrap присутствует; цена **not measured**, pipeline coverage имеет ценность | Измерить setup одной existing HTTP suite; прототип narrow module только при значимой цене и с сохранёнными guard/pipe/filter outcomes |
|
||||
|
||||
### Основания для library-specific решений
|
||||
|
||||
- Vitest lifecycle/file isolation и default stack hooks сверены с документацией через Context7 `/vitest-dev/vitest/v5.0.3`, `docs/guide/lifecycle.md`, `docs/config/sequence.md`. В проекте manifest `5.0.0`; документация ближайшей версии использована для общих lifecycle semantics, не как proof прогона текущего runner. File isolation не сбрасывает все resources после каждого case автоматически.
|
||||
- TanStack Query testing/cache guidance сверено через Context7 `/tanstack/query`, testing guide и core GC implementation. Fresh client/retry-off соответствует текущей задаче. Совет `gcTime=Infinity` относится к определённому Jest handle symptom и не является основанием без воспроизведения добавлять его в frontend Vitest.
|
||||
- Decision о сохранении маленьких backend deferred helpers основан на local `tsconfig.json:13` и реальных consumers, а не на недоступном для default lib API.
|
||||
|
||||
### Финальная проверка отчёта
|
||||
|
||||
- Findings ограничены audit 09; проверены owner/consumer, контрпримеры, минимальная правка и сохраняемые guarantees.
|
||||
- Числа — file/import counts, static workload selection и один explicitly logged targeted unit run; runtime экономия не приписывалась.
|
||||
- Проверены source references и существование цитируемых путей; новый файл просмотрен отдельно, выполнен whitespace check.
|
||||
- Финальная проверка HEAD/status четырёх репозиториев подтверждает исходные SHA и отсутствие staged/unstaged/untracked изменений. Отчёт находится вне них; root ignore/allowlist не менялись.
|
||||
|
||||
## Таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| TI-001 | FIX | P2 | High | backend | test/bank-cards.e2e-spec.ts | Межтестовая category dependency мешает самостоятельному запуску финансовых scenarios | M | TI-002, TI-003 |
|
||||
| TI-002 | FIX | P2 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; test/all-accounts.e2e-spec.ts; test/tbank-connection.e2e-spec.ts; test/tbank-local-reads.e2e-spec.ts; test/tbank-income-local.e2e-spec.ts; test/tbank-ledger-local.e2e-spec.ts; test/tbank-read-baseline.e2e-spec.ts | Фоновый tick способен менять fixtures/counters вне Act и загрязнять read benchmark | S | TI-001, TI-003, TI-005 |
|
||||
| TI-003 | FIX | P2 | High | backend | test/tbank-metadata-persistence.e2e-spec.ts; test/tbank-catalog-discovery.e2e-spec.ts; test/tbank-operations-sync.e2e-spec.ts; src/modules/investments/portfolio/application/portfolio.service.spec.ts | Readiness sleeps и ignored done делают lifecycle зависимым от scheduling | M | TI-001, TI-002, TI-004 |
|
||||
| TI-004 | FIX | P2 | High | backend | test/sqlite.cjs; test/fixtures/tbank-sync-crash.cjs; test/tbank-operations-sync.e2e-spec.ts; package.json; README.md | Targeted crash path допускает source/dist mismatch без fresh-build prerequisite | S | TI-003, TI-005 |
|
||||
| TI-005 | SIMPLIFY | P3 | High | backend | test/sqlite-regression.cjs; test/sqlite.cjs; README.md | Opt-in infrastructure regression выполняет 95 functional suite invocations в normal checks | M | TI-002, TI-004 |
|
||||
| TI-006 | DELETE | P3 | High | frontend | src/pages/bond/ui/BondPage/BondPage.test.tsx | Unused failBond option добавляет ветвь helper без consumer | S | — |
|
||||
@@ -0,0 +1,504 @@
|
||||
# Аудит 10 — Custom Scripts & Tooling
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID:** 10 / Custom Scripts & Tooling.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
- **Задание:** `audit-prompts/10-custom-scripts-and-tooling.md`.
|
||||
- **Результат:** `audit-results/10-custom-scripts-and-tooling.md`. Имя без суффикса `-report` выбрано по прямому указанию пользователя.
|
||||
- **Режим:** read-only для исследованных репозиториев; единственная подготовленная запись в workspace — этот отчёт.
|
||||
- **Skill:** установленный `ponytail-audit` прочитан и применён к сложности инструментов. Его complexity-pass отделён от проверки побочных эффектов и границ гарантий. Исправления не применялись.
|
||||
- **Graphify:** инструкции прочитаны; существующий `graphify-out/graph.json` в workspace не найден. Extraction/install не запускались: они создают дополнительные артефакты, не предусмотренные read-only контрактом аудита.
|
||||
- **Прежний аудит:** `audit-results/technical-audit-2026-10-11.md` прочитан как реестр гипотез. Findings этого отчёта получены из текущих файлов и callers независимо.
|
||||
|
||||
### Репозитории и состояние
|
||||
|
||||
Пути в таблице относительны к workspace; evidence далее использует тот же префикс.
|
||||
|
||||
| Repo path | HEAD SHA | `git status --short` при исследовании | Охват |
|
||||
| --- | --- | --- | --- |
|
||||
| `.` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | Пустой вывод | Workspace manifest, release config, CI и allowlist |
|
||||
| `frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | Пустой вывод | Полный инвентарь custom tooling |
|
||||
| `backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | Пустой вывод | Полный инвентарь custom tooling |
|
||||
| `specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | Пустой вывод | Полный инвентарь custom tooling |
|
||||
| `docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | Пустой вывод | Полный инвентарь custom tooling |
|
||||
| `deploy` | `a56ed85230ae27c1e7155f0cc74ecb33fb9d5644` | Пустой вывод | Только обнаружение layout и Git metadata; вне четырёх явно перечисленных targets |
|
||||
|
||||
`deploy` — дополнительный самостоятельный checkout, а не root-owned каталог. В нём обнаружен `deploy.sh`, но его реализация и `tests/` не исследовались. Утверждение о полном инвентаре ниже относится к четырём targets промпта, а не ко всем возможным deployment-инструментам workspace.
|
||||
|
||||
Root исключает `audit-results/` из tracking через `.gitignore:1–7`; чистый root status сам по себе не подтверждает наличие или качество нового отчёта. Файл проверяется отдельно.
|
||||
|
||||
### Что запускалось
|
||||
|
||||
1. `git rev-parse HEAD`, `git status --short`, `git ls-files` в указанных checkout.
|
||||
2. Статические чтения manifests, hooks, workflows, конфигураций и всех найденных custom tooling files; `rg --files --hidden --no-ignore` и адресные content searches.
|
||||
3. Context7 для актуальной семантики `openapi-typescript --check`, release-it Git checks и npm/npx fallback; установленные реализации генератора и release-it проверены отдельно.
|
||||
4. Две небольшие read-only проверки установленного генератора из `frontend`:
|
||||
|
||||
```sh
|
||||
source "$HOME/.nvm/nvm.sh" && nvm use --silent && node node_modules/openapi-typescript/bin/cli.js src/shared/api/spec/openapi.json -o src/shared/api/generated/schema.d.ts --check
|
||||
source "$HOME/.nvm/nvm.sh" && nvm use --silent && node node_modules/openapi-typescript/bin/cli.js src/shared/api/spec/openapi.json -o packages/icons/src/types.ts --check
|
||||
```
|
||||
|
||||
Первая завершилась успешно и вывела `openapi-typescript 7.13.0`. Вторая намеренно сравнивала результат с другим существующим файлом и получила `Generated types are not up-to-date!` — ветвь отказа с exit 1. Это проверка отрицательного исхода без изменения snapshot или generated types, а не симуляция обновления backend schema. После запусков frontend status оставался пустым.
|
||||
|
||||
Первоначальный вызов `nvm use --silent` не выполнился: shell не загрузил функцию `nvm`. Node-команда тогда не запускалась. Проблема устранена загрузкой существующего `$HOME/.nvm/nvm.sh`; установка Node не потребовалась.
|
||||
|
||||
**Не запускались:** `npm ci/install/audit`, custom scripts, release preview/prepare, codegen без `--check`, Prisma migration/generate, E2E/SQLite/durability/container runners, full `verify`, сборки, публикация images, broker/bank API, OpenSpec CLI и новые исполняемые инструменты. Runtime поведение этих runners оценено по коду, не воспроизведено.
|
||||
|
||||
**Проверки сохранённого отчёта:**
|
||||
|
||||
- `source "$HOME/.nvm/nvm.sh" && nvm use --silent && node docs/node_modules/markdownlint-cli/markdownlint.js --config docs/.markdownlint.json -- audit-results/10-custom-scripts-and-tooling.md` — **PASS**, без `--fix`. Первый запуск выявил только MD029 в нумерации двух списков; нумерация исправлена, повторный запуск прошёл.
|
||||
- `git diff --no-index --check -- /dev/null audit-results/10-custom-scripts-and-tooling.md` — **PASS**; проверяет и новый ignored report, который обычный diff не охватывает.
|
||||
- `git diff --check` в root и четырёх targets — **PASS**; `git status --short` во всех пяти — пустой вывод. `git status --short --ignored -- audit-results/10-custom-scripts-and-tooling.md` подтвердил ожидаемый ignored-каталог.
|
||||
- Семантически сверены inventory totals, callers, scope, evidence и различие confirmed/UNVERIFIED. Boy Scout review ограничен новым отчётом: конкурирующие отчёты/инструкции не создавались, исходники и прежние audit files сохранены.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт:** основной tooling уже делегирует штатным ESLint, TypeScript, Vitest/Jest, dependency-cruiser, npm, Prisma и release-it. Biome сейчас отсутствует; возвращать его ради сокращения scripts не обосновано.
|
||||
2. **Подтверждённое упрощение:** frontend `check-openapi.mjs` повторяет имеющийся `openapi-typescript --check`. Можно убрать один 45-строчный файл, сохранив `npm run openapi:check` и ту же гарантию сравнения generated types с локальным snapshot — **TOOL-001**.
|
||||
3. **Подтверждённая возможность нежелательного side effect:** pre-commit использует `npx lint-staged` без отказа от установки отсутствующего пакета. При корректном `npm ci` используется локальный пакет; при неполной установке команда допускает registry/cache fallback. Фактическая установка во время commit **UNVERIFIED** — **TOOL-002**.
|
||||
4. **Непроверенная эксплуатационная гипотеза:** у `backend/test/runtime-check.cjs` и `frontend/scripts/check-image-publication.cjs` не найдены локальные исполняющие callers. Это `possible unused`, не доказанный dead code. У второго есть полезный recovery-инвариант, поэтому автоматическое удаление особенно неуместно — **TOOL-003/004**.
|
||||
5. **KEEP:** SQLite isolation/cleanup, crash-process fixture, durability checks, offline backend schema generator и release version guards имеют самостоятельную ответственность. Ни обычный Jest, ни `git diff --exit-code`, ни базовые release-it checks полностью её не заменяют.
|
||||
6. **Граница доказательства:** локальная freshness-проверка generated types не подтверждает свежесть предоставленного snapshot относительно текущего backend. Сравнение backend offline document с configured runtime document принадлежит отдельной backend suite; это правильное разделение ownership.
|
||||
|
||||
Подтверждённых проблем уровня P0–P2 в исследованном tooling не установлено. Рекомендации ниже — небольшие P3 улучшения и проверки ownership. Экономия runtime/CI minutes не измерялась.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Метод и воспроизводимые границы подсчёта
|
||||
|
||||
Инвентарь построен по tracked tree каждого repo и независимому filesystem scan со скрытыми папками. Исключены `.git`, `node_modules`, `dist`, `dist-dev`, `generated`, `coverage`, `storybook-static` и generated `.husky/_`.
|
||||
|
||||
Пример команды обнаружения физических JS/shell-инструментов:
|
||||
|
||||
```sh
|
||||
rg --files --hidden --no-ignore frontend backend specs docs -g '*.sh' -g '*.mjs' -g '*.cjs' -g '*.js' -g '*.py' -g '!**/node_modules/**' -g '!**/.git/**' -g '!**/dist/**' -g '!**/dist-dev/**' -g '!**/coverage/**' -g '!**/generated/**' -g '!**/storybook-static/**' -g '!**/.husky/_/**'
|
||||
```
|
||||
|
||||
Hooks без расширения и TS config entrypoints добавлены из `git ls-files` и manifest/config references. Ранние широкие searches включали lockfile/generated шум; он не использован для подсчёта custom scripts или findings. Установленные library files читались только для проверки семантики замены, в инвентарь project-owned code не включены.
|
||||
|
||||
**Определение метрики:** custom tooling file — project-owned CLI/test runner либо загружаемый helper с собственной validation/execution logic. Декларативная конфигурация штатного инструмента, application entrypoint и обычные application tests отдельным custom script не считаются.
|
||||
|
||||
| Repo | CLI / standalone tooling files | Loaded custom helpers | Git hooks без расширения | Ключи `scripts` в основном manifest | Дополнительные workspace scripts |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `frontend` | 5 | 1 | 2 | 25 | 2 |
|
||||
| `backend` | 9 | 1 | 0 | 27 | 0 |
|
||||
| `specs` | 1 | 0 | 1 | 5 | 0 |
|
||||
| `docs` | 1 | 1 | 0 | 5 | 0 |
|
||||
| **Всего** | **16** | **3** | **3** | **62** | **2** |
|
||||
|
||||
Таким образом, **19 физических custom tooling files + 3 hook entrypoints** и **64 npm script entries**, включая workspace builds. Это разные единицы: npm aliases не являются 64 собственными реализациями. Пересчёт возможен по явным спискам следующих таблиц; секции `scripts` находятся в `frontend/package.json:9–35`, `backend/package.json:11–39`, `specs/package.json:8–14`, `docs/package.json:8–14`, двух package manifests ниже.
|
||||
|
||||
Root отдельно содержит 3 npm entries: `release:preview`, `release:prepare`, `commitlint` (`package.json:8–12`); custom CLI/helper files в его tracked allowlist не найдены. Root `.agents/` — установленный агентский toolbox, не application tooling четырёх targets; стандартные skills и их reference scripts не пересчитывались.
|
||||
|
||||
### 3.2. Complete Custom Script Inventory
|
||||
|
||||
В колонке imports указаны прямые зависимости, существенные для исполнения. Частота означает настроенный trigger, не измеренное количество запусков. Общий владелец — repo, содержащий файл; специальный consumer указан отдельно.
|
||||
|
||||
| ID | Entrypoint / фактическая команда / тип | Imports и inputs | Outputs / side effects | Consumers / triggers | Failure mode, инвариант и последствие удаления |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| F1 | `frontend/scripts/check-openapi.mjs:1–45`; `node scripts/check-openapi.mjs`; validation wrapper | `node:child_process`, `fs/promises`, `os`, `path`; local snapshot, committed types, installed generator | Генерирует в private system temp, сравнивает UTF-8 text, удаляет temp; tracked output не пишет | `openapi:check` → `verify`; локальный вызов, PR/push/image gates | Mismatch/generator failure → nonzero; защита freshness. Удаление без переноса команды теряет gate; штатная замена — TOOL-001 |
|
||||
| F2 | `frontend/scripts/check-release.cjs:1–24`; `node scripts/check-release.cjs`; custom guard | `assert`, `child_process`, `../package.json`; merged semver Git tags | Только чтение Git и version | `after:git:init` → обе release-команды | Version должна равняться последнему merged `vX.Y.Z`, иначе отказ. Без guard повторная подготовка версии может пройти при наличии release-коммита |
|
||||
| F3 | `frontend/scripts/test-eslint.mjs:1–395`; `node --test scripts/test-eslint.mjs`; regression executable | ESLint API, установленные plugin presets, `assert`, `fs.globSync`, `node:test`; synthetic TS/TSX и реальные config scopes | Assertions; `fix:true` применяется к `lintText` в памяти, файлы не переписывает | `test:lint` → `verify`; каждое исполнение canonical gate | Проверяет scope/config, custom rules, formatter stability и сохранение JSX text. Без него lint остаётся, но исчезает proof правильного подключения/исключений |
|
||||
| F4 | `frontend/scripts/test-pre-commit.mjs:1–79`; `node --test scripts/test-pre-commit.mjs`; isolated hook regression | `assert`, `child_process`, `crypto.randomBytes`, `fs`, `os`, `path`, `node:test`; hook, установленный Gitleaks | Private temp Git repo, staged/unstaged synthetic secret, fake `npm/npx`, marker; cleanup | README:24; CI `verify.yml:63` с `PATH=/tmp/gitleaks-bin:$PATH` | Assertions для index-only scan, redaction, fail closed и запрета downstream после отказа. Удаление теряет проверку hook boundary, не заменяется обычным history scan |
|
||||
| F5 | `frontend/scripts/check-image-publication.cjs:1–88`; вручную `node --test scripts/check-image-publication.cjs`; synthetic workflow regression | `assert`, `child_process`, `fs`, `os`, `path`, `node:test`; извлечённый shell block `image.yml`, fake Docker/npm | Temporary binaries, markers, call log; cleanup; реальный registry не используется | Локальный исполняющий caller не найден; manual entrypoint возможен | Проверяет post-push retry, digest-bound labels/smoke и отсутствие второго build. `possible unused`; сохранять до определения владельца — TOOL-004 |
|
||||
| F6 | `frontend/eslint-rules.mjs:1–178`; loaded ESLint plugin | ESLint context/AST; filename, source text | Diagnostics; whitespace fixer при `--fix` | `eslint.config.mjs:23–30` → lint, lint-staged, verify, `test:lint` | 2 custom rules: readability и JSX sibling spacing. Удаление снимает локальные style/placement требования; не снимает типовые React/security rules |
|
||||
| B1 | `backend/scripts/check-release.cjs:1–11`; `node scripts/check-release.cjs`; custom guard | `assert`, `child_process`, package version, merged tags | Только чтение | release-it hook:22 → preview/prepare | Та же release-version защита, что F2; нужна локальная реализация без зависимости от соседнего checkout |
|
||||
| B2 | `backend/scripts/generate-openapi.cjs:1–41`; `node scripts/generate-openapi.cjs`; generator glue | `crypto.randomBytes`, `fs/promises.writeFile`, NestFactory, compiled AppModule/openapi; synthetic env | Перезаписывает `openapi.json` в cwd; создаёт Nest preview, закрывает app; не слушает порт | `openapi:generate` после build; `openapi-contract.e2e-spec.ts:139–151` из temp cwd | Failure → exitCode 1; офлайн публикация contract из DTO/Swagger metadata. Удаление теряет artifact-producing entrypoint; frontend generator его не заменяет |
|
||||
| B3 | `backend/test/sqlite.cjs:1–95`; `node test/sqlite.cjs [suite… / --all]`; custom runner | `child_process.spawn`, `fs`, `os`, `path`; suite filenames, Prisma CLI, Jest config | Для каждой suite private temp SQLite, migration deploy, Jest child process group, cleanup; внешний DATABASE_URL заменяется | `test:e2e`, `test:performance`, `test:sqlite`, `test:openapi-contract`; README, CI verify/image, B4 | Unknown suite, migration/test failure, signals → nonzero. Держит DB isolation, общий URL для migration/Jest, kill/cleanup; стандартный Jest этого не обеспечивает |
|
||||
| B4 | `backend/test/sqlite-regression.cjs:1–96`; `node test/sqlite-regression.cjs`; manual runner proof | `assert`, `child_process.spawn`, `fs`, `os`, `path`; B3, sentinel DB, engine/signal overrides | 3 sequential + 2 parallel full suite passes и 3 failure/signal probes; temp logs/sentinel сохраняются как evidence | README:250–254; не включён в npm `verify` или CI | Assert distinct DBs, cleanup, external sentinel unchanged, exit codes. Удаление теряет runner regression proof; число повторов не равно измеренным CI затратам |
|
||||
| B5 | `backend/test/e2e-env.cjs:1–17`; loaded setup | `os.tmpdir`, `path`; `WL_E2E_DIRECTORY`, DATABASE_URL | При несовпадении temp binding бросает ошибку; задаёт synthetic test env | `test/jest-e2e.json:5`; B10 | Прямой Jest с посторонней DB отвергается до suite. Удаление открывает путь тестам в пользовательскую базу; KEEP |
|
||||
| B6 | `backend/test/eslint-config.cjs:1–146`; `node --test test/eslint-config.cjs`; config regression | ESLint API, `assert`, `fs`, `path`, `node:test`; synthetic snippets/config | В одном тесте создаёт `.eslint-require-*` под repo cwd и удаляет в `finally`; остальные проверки in-memory | `test:lint` → `verify` | Проверяет typed rules, boundaries и source resolution compiled imports. Это не formatter write; удаление теряет proof wiring линтера |
|
||||
| B7 | `backend/test/runtime-check.cjs:1–8`; `node test/runtime-check.cjs`; built-artifact assertion | `assert`, `fs`; `package.json`, built entrypoint, compiled Prisma client | Read-only assertions и module import | Только упоминания в ESLint config/test fixtures; исполняющий caller не найден | Проверяет точную строку `start:prod`, наличие build и import client. `possible unused`; без caller сегодня не добавляет automated protection — TOOL-003 |
|
||||
| B8 | `backend/test/container-smoke.cjs:1–77`; `node test/container-smoke.cjs <image>`; image-boundary test | `assert`, `child_process.execFileSync`, `crypto.randomUUID`; Docker image, CLI | Создаёт disposable volume/container, мигрирует isolated DB, auth HTTP внутри контейнера, restart и cleanup | `test:container`; image workflow:55 передаёт `$IMAGE@$DIGEST` | Missing config, unhealthy runtime, assets/native modules/auth/persistence defects → nonzero. Удаление теряет проверку реально опубликованного image |
|
||||
| B9 | `backend/test/bank-cards-durability.cjs:1–219`; `node test/bank-cards-durability.cjs <baseline-dir>`; historical durability runner | `assert`, `child_process`, `crypto`, `events`, `fs`, `net`, `os`, `path`, `better-sqlite3`; отдельный заранее собранный baseline | Private DB, migrations, real local app HTTP, restart/rollback, backups, retained logs/result.json; child stop в finally | `test:durability`; README:268–293, testing convention:55; manual при data/schema changes | Baseline/current history mismatch и assertions → failure. Защита денежных данных/replay/history через реальные restart/restore boundaries; обычный fresh-DB E2E не равносилен |
|
||||
| B10 | `backend/test/fixtures/tbank-sync-crash.cjs:1–86`; child `fork(..., [checkpoint])`; crash fixture | B5, ConfigService, compiled access/token/sync/repository/Prisma classes; synthetic upstream, temp DB | Real staging/publication в test DB, IPC checkpoint; process намеренно останавливает родитель SIGKILL | `test/tbank-operations-sync.e2e-spec.ts:424–488` → B3/`test:e2e` | Failure до checkpoint → error; демонстрирует durable boundaries между страницами/до/после publish. Изоляция отдельного процесса принципиальна; mocks не заменяют SIGKILL |
|
||||
| S1 | `specs/scripts/check-release.cjs:1–10`; `node scripts/check-release.cjs`; release guard | `assert`, `child_process`, package version/tags/porcelain status | Только чтение; rejects untracked/staged/unstaged files и version mismatch | release hook:22 → preview/prepare | Сохраняет clean-tree включая untracked и запрет repeated bump; native release-it имеет более узкую clean-tree проверку |
|
||||
| D1 | `docs/scripts/check-release.cjs:1–10`; та же команда | Те же imports/inputs, но только локальный docs checkout | Только чтение | release hook:22 → preview/prepare | Те же гарантии, что S1. Копия между самостоятельными repos не доказывает ненужную общую abstraction |
|
||||
| D2 | `docs/.markdownlint-rule-relative-links.cjs:1`; loaded adapter | Установленный `markdownlint-rule-relative-links`, его `.default` export | Экспорт rule, без собственного validation algorithm | `docs/package.json:9`: обе ветви Markdown lint | Однострочный CommonJS adapter; полезная совместимость. Без него нужно доказать корректную загрузку package export штатным CLI |
|
||||
|
||||
#### Git hooks и прочие config entrypoints
|
||||
|
||||
| Entrypoint | Execution graph / inputs | Side effects / failure / invariant |
|
||||
| --- | --- | --- |
|
||||
| `frontend/.husky/pre-commit:1–3` | `gitleaks git --pre-commit --staged --redact=100 --no-banner` → `npx lint-staged` → `npm run deps` | Scan index; lint-staged намеренно автоисправляет staged JS/TS; deps собирает packages и проверяет graph. Secret scan failure останавливает downstream. npx install fallback — TOOL-002 |
|
||||
| `frontend/.husky/commit-msg:1` | `npx --no-install commitlint --edit "$1"` | Read commit message, reject invalid format; без package download fallback |
|
||||
| `specs/.githooks/commit-msg:1–2` | `npx --no-install commitlint --edit "$1" --strict` | Та же responsibility; подключается npm `prepare` через `git config core.hooksPath .githooks` |
|
||||
| `frontend/dependency-cruiser.cjs:1–88` | Штатные declarative `forbidden` rules/options | Cycles, unresolved dependencies, layers/public APIs, production→tests; собственного graph parser нет |
|
||||
| `frontend/.storybook/dependency-cruiser.cjs:1–23` | Расширяет base config; `reachable: true`, `tsPreCompilationDeps: false` | Дополнительно запрещает transitive reachability stories→application data. Однострочный direct-import lint не равносилен |
|
||||
| `frontend/vite.config.ts:15–20` | PWA `configureOptions` проверяет наличие Storybook plugin | Меняет только build plugin options, отключая worker в каталоге; installed tool integration, не отдельный executable framework |
|
||||
| `backend/prisma.config.ts:1–13` | Prisma CLI загружает dotenv/config и native `defineConfig` | Удаляет `RUST_LOG` только из текущего CLI process env. Это compatibility shim с описанным historical failure; актуальная необходимость не воспроизводилась |
|
||||
| 4 repo-local `commitlint.config.cjs` | `@commitlint/config-conventional` + declarative type enum | Нативная конфигурация; не custom parser и не отдельный gate implementation |
|
||||
|
||||
### 3.3. Package script graph — все aliases и реальные префиксы
|
||||
|
||||
#### Frontend — 25 entries
|
||||
|
||||
| Script names | Команды / порядок |
|
||||
| --- | --- |
|
||||
| `dev` | `npm run build:packages && vite` |
|
||||
| `dev:catalog` | `npm run build:packages && storybook dev -p 6006 --no-open --disable-telemetry` |
|
||||
| `build:catalog` | `npm run build:packages && storybook build --output-dir storybook-static --disable-telemetry` |
|
||||
| `preview:catalog` | `vite preview --outDir storybook-static` |
|
||||
| `typecheck` | `npm run build:packages && tsc -b --noEmit` |
|
||||
| `build` | `npm run build:packages && tsc -b && vite build` |
|
||||
| `lint` → `lint:eslint` | `npm run lint:eslint` → `npm run build:packages && eslint . --max-warnings=0` |
|
||||
| `lint:fix` | `npm run build:packages && eslint . --fix --max-warnings=0` |
|
||||
| `test`, `test:run`, `test:coverage` | Каждый сначала `npm run build:packages`, затем соответственно `vitest`, `vitest run`, `vitest run --coverage` |
|
||||
| `test:lint` | `node --test scripts/test-eslint.mjs` |
|
||||
| `openapi:generate` | `openapi-typescript src/shared/api/spec/openapi.json -o src/shared/api/generated/schema.d.ts` |
|
||||
| `openapi:check` | `node scripts/check-openapi.mjs` |
|
||||
| `knip` | `npm run build:packages && knip` |
|
||||
| `deps` | `npm run build:packages && depcruise src packages --config dependency-cruiser.cjs --output-type err-long` |
|
||||
| `deps:storybook` | `npm run build:packages && depcruise src packages .storybook/preview.tsx --config .storybook/dependency-cruiser.cjs --output-type err-long` |
|
||||
| `verify` | `build:packages` → `openapi:check` → `tsc -b --noEmit` → `eslint . --max-warnings=0` → `test:lint` → `vitest run --coverage` → `knip` → оба depcruise commands без повторного npm prefix → `tsc -b` → `vite build` → `storybook build --output-dir storybook-static --disable-telemetry` |
|
||||
| `prepare` | `husky` |
|
||||
| `preview` | `vite preview` |
|
||||
| `release:preview`, `release:prepare` | `release-it --dry-run --ci`, `release-it --ci`; оба загружают local release hook |
|
||||
| `commitlint` | `commitlint` |
|
||||
| `build:packages` | `npm run build --workspaces --if-present` |
|
||||
|
||||
Workspace entries: `packages/icons/package.json:19–21` и `packages/ui/package.json:23–25` содержат по одному `build = vite build && tsc -p tsconfig.json`. `packages/tokens/package.json:1–12` не содержит scripts; `--if-present` намеренно его пропускает.
|
||||
|
||||
#### Backend — 27 entries
|
||||
|
||||
| Script names | Команды / порядок |
|
||||
| --- | --- |
|
||||
| `build` | `prisma generate && nest build` |
|
||||
| `openapi:generate` | `npm run build && node scripts/generate-openapi.cjs` |
|
||||
| `format` → `lint:fix` | `npm run lint:fix` → `eslint . --fix --max-warnings=0` |
|
||||
| `start` | `nest start` |
|
||||
| `start:dev` | `NODE_ENV=development nest start --watch --path tsconfig.dev.json` |
|
||||
| `start:debug` | `nest start --debug --watch --path tsconfig.dev.json` |
|
||||
| `start:prod` | `node dist/src/main.js` |
|
||||
| `lint` → `lint:check` | `npm run lint:check` → `eslint . --max-warnings=0` |
|
||||
| `typecheck:app`, `typecheck:test` | `tsc --project tsconfig.typecheck.app.json`, `tsc --project tsconfig.typecheck.test.json` |
|
||||
| `test` | `node --experimental-vm-modules node_modules/jest/bin/jest.js` |
|
||||
| `test:watch`, `test:cov` | `npm test -- --watch`, `npm test -- --coverage` |
|
||||
| `test:debug` | `node --experimental-vm-modules --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand` |
|
||||
| `test:e2e` | `npm run build && node test/sqlite.cjs --all` |
|
||||
| `test:performance` | `npm run build && node test/sqlite.cjs tbank-read-baseline.e2e-spec.ts` |
|
||||
| `test:sqlite` | `node test/sqlite.cjs`; default: auth + bank-cards suites |
|
||||
| `test:container` | `node test/container-smoke.cjs`; caller должен передать image |
|
||||
| `test:durability` | `node test/bank-cards-durability.cjs`; caller должен передать baseline |
|
||||
| `test:openapi-contract` | `npm run build && node test/sqlite.cjs openapi-contract.e2e-spec.ts` |
|
||||
| `verify` | `npm run lint && npm run test:lint && npm run typecheck:app && npm run typecheck:test && npm run test:cov -- --runInBand && npm run build` |
|
||||
| `release:preview`, `release:prepare` | `release-it --dry-run --ci`, `release-it --ci`; оба загружают local release hook |
|
||||
| `commitlint` | `commitlint` |
|
||||
| `test:lint` | `node --test test/eslint-config.cjs` |
|
||||
|
||||
Важно: `test:sqlite`, `test:container`, `test:durability` сами не собирают backend. Наличие уже подготовленного build/image/baseline — явный input, а не скрытый auto-build/install.
|
||||
|
||||
#### Specs и docs — по 5 entries
|
||||
|
||||
| Repo / script | Реальная команда |
|
||||
| --- | --- |
|
||||
| `specs/verify` | `git diff --check` |
|
||||
| `specs/prepare` | `git config core.hooksPath .githooks` |
|
||||
| `specs/release:preview`, `specs/release:prepare`, `specs/commitlint` | `release-it --dry-run --ci`, `release-it --ci`, `commitlint` |
|
||||
| `docs/lint` | `markdownlint --dot --rules .markdownlint-rule-relative-links.cjs --ignore node_modules --ignore CHANGELOG.md -- "**/*.md" && markdownlint --config .markdownlint-changelog.json --rules .markdownlint-rule-relative-links.cjs -- CHANGELOG.md` |
|
||||
| `docs/verify` | `npm run lint && git diff --check` |
|
||||
| `docs/release:preview`, `docs/release:prepare`, `docs/commitlint` | `release-it --dry-run --ci`, `release-it --ci`, `commitlint` |
|
||||
|
||||
`specs/verify` проверяет whitespace diff, не валидность behavioral specifications. Это ограничение команды, а не обнаруженный custom validator или причина вводить новый framework в этом аудите.
|
||||
|
||||
### 3.4. Scripts Dependency DAG
|
||||
|
||||
```text
|
||||
frontend CI verify / image + local engineering gate
|
||||
-> npm run verify
|
||||
-> npm run build:packages
|
||||
-> npm run build --workspaces --if-present
|
||||
-> icons, ui: vite build -> tsc -p
|
||||
-> npm run openapi:check -> check-openapi.mjs -> installed openapi-typescript
|
||||
-> ESLint -> eslint.config.mjs -> eslint-rules.mjs + native plugins
|
||||
-> npm run test:lint -> node --test test-eslint.mjs -> ESLint API
|
||||
-> Vitest -> JSDOM project + browser project -> Playwright/Chromium
|
||||
-> Knip
|
||||
-> dependency-cruiser [base config]
|
||||
-> dependency-cruiser [storybook config -> base, reachable rule]
|
||||
-> tsc -> Vite/PWA -> Storybook
|
||||
frontend security job
|
||||
-> standard sha256sum -> downloaded Gitleaks -> history scan
|
||||
-> PATH=/tmp/gitleaks-bin:$PATH node --test test-pre-commit.mjs
|
||||
-> synthetic temp Git repo -> real .husky/pre-commit
|
||||
-> Gitleaks -> fake npx/npm markers
|
||||
frontend Git pre-commit
|
||||
-> staged/redacted Gitleaks -> npx lint-staged [ESLint --fix] -> npm run deps
|
||||
frontend image workflow
|
||||
-> inline version/lock/SHA assertions -> immutable publish/resume
|
||||
-> Docker digest -> pull by digest -> label assertions -> nginx smoke
|
||||
check-image-publication.cjs [manual entrypoint; caller UNVERIFIED]
|
||||
-> extracts this workflow shell block -> fake Docker/npm -> recovery assertions
|
||||
|
||||
backend CI verify / image + local engineering gate
|
||||
-> Prisma generate [native config -> process-local RUST_LOG shim]
|
||||
-> npm run verify
|
||||
-> ESLint -> native config/plugins
|
||||
-> node --test eslint-config.cjs -> ESLint API
|
||||
-> both tsc projects -> Jest coverage -> build [Prisma -> Nest]
|
||||
-> npm run test:e2e -> build -> sqlite.cjs --all
|
||||
-> per-suite mkdtemp / fixed test DB -> Prisma migrate deploy
|
||||
-> Jest --config test/jest-e2e.json --runInBand --runTestsByPath
|
||||
-> e2e-env.cjs [reject non-runner database]
|
||||
-> openapi-contract suite -> generate-openapi.cjs in temp cwd
|
||||
-> compiled AppModule + openapi -> Nest preview -> temp openapi.json
|
||||
-> operations-sync suite -> fork tbank-sync-crash.cjs -> IPC -> SIGKILL
|
||||
-> process-group stop / directory cleanup
|
||||
backend manual SQLite regression -> sqlite-regression.cjs -> sqlite.cjs
|
||||
backend manual durability -> bank-cards-durability.cjs
|
||||
-> historical/current builds -> isolated migration + real local HTTP
|
||||
-> restart / application rollback / SQLite backup-restore -> retained evidence
|
||||
backend image workflow
|
||||
-> inline version/lock/SHA assertions -> Docker publish -> digest pull
|
||||
-> npm run test:container -- IMAGE@DIGEST -> container-smoke.cjs
|
||||
runtime-check.cjs [manual entrypoint; caller UNVERIFIED]
|
||||
-> package script string + built main presence + compiled Prisma import
|
||||
|
||||
frontend/backend/specs/docs release:preview or release:prepare
|
||||
-> release-it -> native branch/clean/commits checks
|
||||
-> after:git:init -> local check-release.cjs -> package version vs merged tags
|
||||
-> specs/docs additionally check porcelain, including untracked files
|
||||
specs npm install lifecycle -> prepare -> local Git hook config
|
||||
specs commit-msg -> local commitlint
|
||||
specs CI verify -> npm run verify -> git diff --check
|
||||
docs CI verify -> npm run verify -> Markdown lint
|
||||
-> one-line relative-links adapter -> installed relative-links rule
|
||||
-> git diff --check
|
||||
all four security workflows -> sha256sum -> Gitleaks; npm audit separately
|
||||
all four PR commit workflows -> commitlint title + BASE_SHA..HEAD_SHA
|
||||
root PR workflow -> npm exec --no -- commitlint; no cross-repo script delegation
|
||||
```
|
||||
|
||||
DAG показывает ownership и ordering. Количество запусков, required-check настройки и эффект repeated gates относятся к аудитам 11/14; здесь не измерялись.
|
||||
|
||||
### 3.5. SHA/Fingerprint Mechanism Registry
|
||||
|
||||
Категории промпта: **a** business idempotency/replay; **b** storage integrity; **c** generated artifact freshness; **d** CI/cache identity; **e** test determinism; **f** ad hoc guard. Runtime resource revision/identity отдельно помечены как расширение категории d: это не CI cache. Crypto authentication и подписанный cursor указаны отдельно, чтобы не назвать их tooling checks.
|
||||
|
||||
Для всех project-owned SHA-256 fingerprints ниже код исходит из практической уникальности digest; отдельных collision detectors не найдено. Это не доказательство криптографической модели — её аудит здесь не проводился. Обычный SHA не является подписью, шифрованием или authorization.
|
||||
|
||||
| Mechanism / category | Evidence / caller | Binding input / угроза / assumptions | Альтернативная гарантия и решение |
|
||||
| --- | --- | --- | --- |
|
||||
| Card/operation creation fingerprints — **a** | `backend/src/modules/banking/bank-cards/application/bank-card-input.ts:74–81`; `backend/src/modules/banking/bank-cards/application/bank-cards.service.ts:74–95`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:214–239` | SHA-256 от normalized input; BigInt сериализуется decimal string. UUID и owner проверяются отдельно; одинаковый intent возвращается, конфликт payload отвергается, tombstone не позволяет воссоздать удалённую operation | Unique ID один не обнаружит изменённый payload; current row comparison после редактирования не равно original intent. **KEEP**, сохранять ownership/transaction/tombstone |
|
||||
| Refresh-token hash + CAS — authentication / replay, близко **a**, не tooling | `backend/src/modules/auth/application/auth.service.ts:81–113,166–205`; test helper `backend/src/modules/auth/application/auth.service.spec.ts:24–25` | SHA-256 всего token; session/sub/expiry проверяются отдельно; updateMany связывается с прежним hash. Test helper вычисляет expected value тем же стандартным primitive | JWT verify один не заменяет stored-token rotation/CAS. **KEEP**; security оценка — audit 13 |
|
||||
| Password hash — credential storage, вне tooling categories | `backend/src/modules/auth/application/auth.service.ts:19–26,48–53,64–75` | bcrypt rounds 12, библиотечный salt; password input; dummy hash для отсутствующего user. Не SHA checksum и не storage-file integrity | Без равносильного password KDF замену не предлагать. **KEEP**; dependency/security rationalization здесь не выполнялась |
|
||||
| Operations cursor binding — runtime pagination identity, **d-runtime** | `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:128–144`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:239,331–339` | SHA-256 от user, connection revision, membership revision для all, account operations revisions и filter key; rejects cursor после изменения scope/data | HMAC защищает body от подделки, но сам по себе не проверяет актуальность binding. **KEEP** |
|
||||
| Operations cursor HMAC — authentication of opaque cursor | `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:347–388`; read → encode/decode | HMAC-SHA256 от base64url JSON body под JWT_SECRET; length + timingSafeEqual; repository отдельно проверяет binding | Неподписанный base64 не равносилен. **KEEP**; key reuse/полнота validation относятся к audit 13 |
|
||||
| Operations work scope key — durable deduplication, **a** | `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:254–269`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:157–167` | SHA-256 от отсортированных unique interval keys; owner/revision входят в DB identity отдельно. Угроза — дублирующиеся work intents | Raw canonical JSON можно рассматривать только с DB key/migration proof; отдельная новая защита не нужна. **KEEP** |
|
||||
| Operations resource key — **d-runtime** | `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:237–253`; controller read → response metadata | SHA-256 filterKey, base64url; связывает metadata identity с фильтром, не с authenticator | Можно передавать raw filterKey только после проверки wire consumers/compatibility. Экономия не измерена. **KEEP** |
|
||||
| Aggregated accounts data revision — **d-runtime** | `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts:16–65` → `investmentResourceMeta`; `frontend/src/shared/api/resource-query.ts:129–148` проецирует meta | SHA-256 membership revision и списка account IDs/portfolio data revisions. Hash revision graph, не всего response и не schema | Monotonic stored revision требует другого owner/state; простое UUID на каждый GET меняет стабильность. **KEEP** |
|
||||
| Candle data revision — **d-runtime** | `backend/src/modules/investments/instruments/infrastructure/candle.repository.ts:30–53`; `backend/src/modules/investments/instruments/application/candle.service.ts:101–128` | SHA-256 JSON упорядоченных candle time/close/isComplete; coverage/freshness отражаются отдельно. Same digest не доказывает same coverage | Timestamp/revision может заменить только после определения равносильной change semantics. **KEEP** |
|
||||
| Downloaded Gitleaks archive checksum — **b**, distribution integrity | `frontend/.gitea/workflows/verify.yml:52–58`; `backend/.gitea/workflows/verify.yml:52–58`; `specs/.gitea/workflows/security.yml:24–30`; `docs/.gitea/workflows/security.yml:24–30` | Pinned SHA-256 целого tar.gz до extraction/execution; checksum доверяется из tracked workflow. Угроза — получение иных bytes | HTTPS без pin не доказывает совпадение bytes с ожидаемым artifact. Используется native `sha256sum --check`, custom checker нет. **KEEP** |
|
||||
| OCI image digest + source SHA binding — **d**, artifact identity | `frontend/.gitea/workflows/image.yml:46–62`; `backend/.gitea/workflows/image.yml:42–56`; F5/B8 | Docker-generated manifest digest; pull/smoke по `IMAGE@DIGEST`, revision/version OCI labels проверяются отдельно; source SHA задан event/checkout | Version tag или label отдельно не связывает проверку с тем же artifact. Native Docker handles digest; не собственный hashing framework. **KEEP** |
|
||||
| Event HEAD/PR revision SHA — **d**, source identity | Image workflows:26–27; four commits workflows; verify log SHA | Git HEAD равен event SHA для publication; commitlint диапазон BASE..HEAD; SHA log — traceability. Не equivalent runtime provenance proof | **KEEP** native Git assertions; source SHA не доказывает schema freshness |
|
||||
| npm lockfile SRI — **b**, package artifact integrity | Четыре `package-lock.json`, например `backend/package-lock.json:85`; consumers — npm CLI / `npm ci` | Library-maintained `integrity`, обычно SHA-512; input — package tarball. Не custom registry | **KEEP** package manager ownership. Lock entries не считались отдельными custom mechanisms |
|
||||
| Frontend generated-types freshness — **c**, hashing нет | F1; `frontend/package.json:22–26`; local generator CLI:125–134,280–292 | Exact generated UTF-8 text сравнивается с committed text. Проверяет воспроизводимость относительно local snapshot, не backend актуальность | Native generator `--check` обеспечивает ту же гарантию — **SIMPLIFY** TOOL-001 |
|
||||
| Backend offline/runtime OpenAPI equality — **c**, hashing нет | B2; `backend/test/openapi-contract.e2e-spec.ts:126–159` | Generated JSON из compiled preview сравнивается с configured app document; child network listen/connect запрещены; temp artifact | JSON equality дополняется HTTP behavior tests, не SHA. **KEEP**; не переносить чтение backend source во frontend gate |
|
||||
| SQLite integrity/foreign keys — **b**, hashing нет | B8:63; B9:109–118,202–205; `backend/test/tbank-migration.e2e-spec.ts:78–79,259–260` | Native `PRAGMA integrity_check` / `foreign_key_check` после runtime/migration/restore boundaries | Filesystem byte checksum не доказывает relational integrity. **KEEP** native DB checks |
|
||||
| Release version / migration-prefix guards — **f**, hashing нет | F2/B1/S1/D1; B9:13–23 | Version vs accepted merged semver; historic migration bytes unchanged; synthetic baseline/current directories distinct | Native release-it checks частично покрывают prerequisites, но не всю семантику; **KEEP** |
|
||||
| Synthetic determinism — **e**, собственный hash не найден | F4:22; F5:38–46; B4:14–18; B9:88–106 | Random temp IDs/secret и фиксированный fake digest `sha256:exact`; deterministic fixtures/assertions. Fake digest не вычисляется и не является security evidence | **KEEP** isolation; не добавлять hash registry для тестов |
|
||||
| PWA generated precache revisions — **d**, dependency-owned cache identity | `frontend/vite.config.ts:12–51`; Vite/PWA/workbox build | Asset content revisions обслуживает установленный генератор; own glob/static-shell policy, API paths excluded from navigation fallback | Это generated output, не custom SHA checker. **KEEP** library ownership; build artifacts не аудитировались |
|
||||
|
||||
**Не найдено:** project-owned SHA/fingerprint registry для FSD, conventions, specs или generated freshness; custom CI cache hash scripts; snapshot-update scripts в канонических commands. Наличие слова `snapshot` в financial DTO/tests не классифицировалось как test snapshot tooling.
|
||||
|
||||
### 3.6. Native Tool Replacement Matrix
|
||||
|
||||
| Custom mechanism / wrapper | Проверенная native возможность | Реальная эквивалентность / решение |
|
||||
| --- | --- | --- |
|
||||
| F1 OpenAPI freshness wrapper | Installed `openapi-typescript@7.13.0 --check -o`; CLI:125–134 и 280–292 | **Да**, для нынешнего single local schema/output. Генерация в памяти, сравнение, exit 0/1 до write. Перенести команду и удалить F1 |
|
||||
| F1 через regenerate + `git diff --exit-code` | Git показывает изменение tracked result | **Нет** для read-only verification: генерация сначала перепишет result; обычный diff не видит untracked. Native generator check проще и точнее |
|
||||
| Release clean-tree guard S1/D1 | release-it `requireCleanWorkingDir: true` | **Не полностью:** installed `docs/node_modules/release-it/lib/plugin/git/Git.js:126–130` использует `git diff --quiet HEAD`, custom guard использует porcelain status и видит untracked. Не удалять assertion как «дубликат» |
|
||||
| Version-vs-last-tag guard F2/B1/S1/D1 | release-it `requireBranch`, `requireCommits`; `npm.ignoreVersion` | **Нет**: commits-after-tag не проверяет equality package version/accepted tag; `ignoreVersion` меняет источник версии. Сохранить guard |
|
||||
| SQLite scheduling B3 | Jest `--runTestsByPath`, `--runInBand`, config/setupFiles | Native Jest уже используется. **Нет** замены mkdtemp, migration binding, override external URL, process-group cleanup. KEEP thin custom orchestration |
|
||||
| E2E environment safety B5 | Jest setupFiles | Native hook — caller, не сама гарантия безопасной DB. KEEP 17-строчный guard |
|
||||
| Crash/durability/image checks | Jest/Vitest mocks, Playwright browser tests, TypeScript compile | **Нет**: не воспроизводят SIGKILL, historical migrations/restore и actual Docker packaging. KEEP boundary-specific runners |
|
||||
| ESLint config tests F3/B6 | `eslint .`, `--max-warnings=0`, ESLint API / native RuleTester | Native CLI проверяет current code, но не synthetic counterexamples wiring. Применён native API; новый validator/framework не нужен |
|
||||
| F6 JSX-spacing | Installed `@stylistic/jsx-newline`, `prevent:false`; `frontend/node_modules/@stylistic/eslint-plugin/dist/rules/jsx-newline.js:43–84` | **Не drop-in:** native rule обрабатывает все JSXExpressionContainer и gaps, custom — только выражения с JSX, не text interpolation; алгоритмы различны. Сначала определить нужную policy, не заменять вслепую |
|
||||
| F6 readability | Native `no-restricted-syntax` / `no-restricted-imports`, TypeScript rules | Части placement/style policy можно выразить selectors; единой эквивалентной замены всего plugin не установлено. Наличие syntax rule не доказывает нужность самой convention. KEEP до доказанной цены/false positives |
|
||||
| FSD/story isolation | dependency-cruiser rules с `circular`, `couldNotResolve`, `reachable`, capture groups | Уже native declarative config, не custom graph analyzer. Direct ESLint import запрет не покрывает transitive story reachability. KEEP |
|
||||
| D2 Markdown adapter | markdownlint CLI `--rules` + installed relative-links package | Однострочный adapter уже минимален; semantic link validation принадлежит dependency. Не добавлять собственный link checker |
|
||||
| Named npm aliases | `npm run`, workspaces `--if-present` | Stable developer commands полезны. Нельзя считать одно делегирование основанием DELETE |
|
||||
| Pre-commit `npx lint-staged` | npm `exec --no` / `npx --no`; старое `--no-install` конвертируется в `--no` | Native отказ от установки покрывает конкретный side effect; использовать его без новой dependency — TOOL-002 |
|
||||
| Biome / Prettier | В manifests не установлены; README frontend:95–99 описывает удаление Biome | Нет основания вводить второй formatter ради сокращения текущего tooling |
|
||||
|
||||
Проверенные внешние источники: OpenAPI TypeScript CLI (`https://github.com/openapi-ts/openapi-typescript/blob/main/packages/openapi-typescript/bin/cli.js`), release-it Git prerequisites (`https://github.com/release-it/release-it/blob/main/docs/git.md`), npm exec/npx (`https://github.com/npm/cli/blob/latest/docs/lib/content/commands/npx.md`). Для version-sensitive решений приоритет отдан установленному коду, не только latest документации.
|
||||
|
||||
### 3.7. Проверки и их side effects
|
||||
|
||||
| Поток | Фактический side effect | Оценка |
|
||||
| --- | --- | --- |
|
||||
| Frontend `verify`, `typecheck`, `lint`, `deps` | Префикс `build:packages` создаёт package dist; verify также coverage, TS/build artifacts, app/catalog/PWA output | Заявленный build-backed gate. Не read-only в строгом файловом смысле, но generated types не переписывает; не подтверждён forbidden tracked rewrite |
|
||||
| Backend `verify` | Coverage, Prisma client generation и compiled build | Заявленные outputs. Нельзя запускать в аудите read-only; не повод удалять build |
|
||||
| `openapi:generate` frontend/backend | Перезаписывает artifact | Явно generation, не check. Не использовать как read-only proof |
|
||||
| F1 и предложенный native `--check` | F1 пишет private temp, native single-schema check не пишет output | Native вариант уменьшает и implementation, и файловые side effects |
|
||||
| F3/B6 `test:lint` | F3 fixes in-memory; B6 temporary source в repo с finally cleanup | Не source auto-fix. При строгом read-only режиме всё равно не запускалось |
|
||||
| Pre-commit | lint-staged auto-fix/stage; packages build; возможный npx install fallback | Auto-fix заявлен README/package config. Установка отсутствующего tool не нужна проверке — TOOL-002 |
|
||||
| Frontend/specs `prepare` lifecycle | Husky setup / изменение local Git `core.hooksPath` | Installation lifecycle, не validator; `npm ci --ignore-scripts` обходит lifecycle в commitlint jobs |
|
||||
| SQLite/E2E/crash runners | Migration/write/delete только runner temp DB; tests могут слушать local HTTP | Поддерживаемая isolation boundary. Не запускать без side-effect review; не заменять на direct Jest с inherited DB |
|
||||
| Container smoke | Docker volume/container, migration/auth write внутри disposable image environment | Явные isolated тестовые изменения, cleanup предусмотрен |
|
||||
| Durability/regression | Retained temporary DB, backups, logs/result.json | Заявленные диагностические outputs в README, не скрытый snapshot update |
|
||||
| Image publication | Docker login/build/push/pull и registry changes | Реальное publication, не безопасная локальная validation команда. Не исполнялось |
|
||||
|
||||
**False positives / negatives:** текущий freshness checker и native replacement требуют exact text equality: различие generator output может блокировать gate даже при эквивалентных TS типах. Это выбранная reproducibility-гарантия; частота такого churn не измерена. Одинаковые snapshot/generated files могут быть совместно устаревшими — это не ложное обещание текущего README, который ограничивает check локальным snapshot. Для F6 синтаксические эвристики не устанавливают semantic ownership: schema branch реагирует на имя метода `refine/superRefine`, helper branch проверяет определённую форму arrow-component. Production false positives/negatives и реальный churn **UNVERIFIED**; отдельный finding без примера текущего consumer не сформирован.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### TOOL-001 — Самописный OpenAPI checker заменяется уже установленным `--check`
|
||||
|
||||
- **Decision:** SIMPLIFY.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High.
|
||||
- **Evidence:** `frontend/scripts/check-openapi.mjs:1–45`; `frontend/package.json:22–26`; `frontend/scripts/test-eslint.mjs:88–90`; `frontend/.ai/conventions/data.md:32–33`. Проверка замены: installed `frontend/node_modules/openapi-typescript/bin/cli.js:125–134,280–292`.
|
||||
- **Claim:** temp-directory/spawn/read/compare/cleanup реализация не добавляет гарантий к нынешнему single-schema native check.
|
||||
- **Current mechanism:** `verify` вызывает `openapi:check`; helper запускает тот же generator с теми же input/options, но с temporary output, затем сравнивает UTF-8 strings и транслирует exit code.
|
||||
- **Impact / actual cost:** один дополнительный 45-строчный file, process wrapper и filesystem lifecycle для сопровождения. **Impact unmeasured** для времени/CI/tokens; генерация всё равно нужна и её стоимость не исчезнет.
|
||||
- **Why now:** native option присутствует в закреплённой версии 7.13.0; положительная и отрицательная ветви read-only CLI проверены на текущем checkout.
|
||||
- **Minimum change:** сохранить alias и задать `openapi:check = openapi-typescript src/shared/api/spec/openapi.json -o src/shared/api/generated/schema.d.ts --check`; удалить только `scripts/check-openapi.mjs`; synthetic ESLint sample filePath:88 заменить на surviving `.mjs` file; data convention:33 описать native check вместо temp implementation.
|
||||
- **Guarantees at risk:** mismatch должен давать nonzero; check не должен чинить generated file, читать соседний backend или обращаться к runtime endpoint. Обновление native version/options должно применяться одинаково к generate/check.
|
||||
- **Counterarguments / trade-offs:** теряется собственное сообщение с подсказкой `npm run openapi:generate`; native diagnostic короче. Custom wrapper оправдан при реальных дополнительных нескольких schemas/outputs или иной семантике сравнения, которых сейчас нет.
|
||||
- **Verification or rollback:** сверить success на committed artifact, rejection на отличающемся output, отсутствие изменений после обоих исходов; при реализации повторить существующий `test:lint`. Rollback — восстановить helper и прежнюю строку alias, без изменения schema/artifact.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** —.
|
||||
|
||||
### TOOL-002 — Pre-commit допускает установку отсутствующего lint tool
|
||||
|
||||
- **Decision:** FIX.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** High для способности fallback; фактическое использование fallback UNVERIFIED.
|
||||
- **Evidence:** `frontend/.husky/pre-commit:1–3`; `frontend/package.json:36–38,106`; `frontend/.husky/commit-msg:1`; `frontend/scripts/test-pre-commit.mjs:38–46,53–74`; npm npx/exec semantics в источниках секции 3.6.
|
||||
- **Claim:** plain `npx lint-staged` не ограничивает hook установленным tool и допускает ненужный install side effect при его отсутствии.
|
||||
- **Current mechanism:** Git pre-commit сначала проходит Gitleaks, затем npx разрешает binary. После нормального `npm ci` локальный `lint-staged@17.5.1` уже существует; при неполной установке npm может запросить/выполнить installation в cache, а не сразу отказать.
|
||||
- **Impact / actual cost:** возможны registry/cache mutation, ожидание сети/подтверждения и запуск tool вне текущей локальной installation. **Impact unmeasured**; проблема не воспроизводилась удалением packages и не заявляется как состоявшаяся установка.
|
||||
- **Why now:** явный no-install pattern уже используется соседним commit-msg hook; изменение одной строки устраняет ненужную возможность побочного эффекта.
|
||||
- **Minimum change:** использовать `npx --no lint-staged`; поддерживаемый existing spelling `npx --no-install lint-staged` также транслируется npm в отказ от установки. Остальные hook steps сохраняются.
|
||||
- **Guarantees at risk:** Gitleaks должен остаться первым, redacted и fail closed; lint-staged должен продолжить intended auto-fix/index handling; dependency graph check не пропускать.
|
||||
- **Counterarguments / trade-offs:** в штатной installation fallback не нужен и не происходит. Hook станет явно требовать подготовленной development installation; он не должен устанавливать её во время commit. Отказ от установки не является полной проверкой provenance уже закешированных/global executables.
|
||||
- **Verification or rollback:** существующий hook regression проверяет order/failure/redaction, но fake npx не доказывает no-install resolution. Самая дешёвая дополнительная проверка — controlled temporary PATH/cache с отсутствующим tool и explicit `--no`, подтверждающая отказ без установки; не трогать текущие node_modules. Rollback — прежняя hook строка.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** —.
|
||||
|
||||
### TOOL-003 — У build runtime assertion не найден исполняющий consumer
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `backend/test/runtime-check.cjs:1–8`; `backend/eslint.config.mjs:67–75`; `backend/test/eslint-config.cjs:14–23`; `backend/package.json:11–39`; `backend/Dockerfile:21–41`; `backend/test/container-smoke.cjs:29–65`; caller search по active manifests/workflows/docs/scripts.
|
||||
- **Claim:** инструмент `possible unused`: references в lint scope/synthetic lint cases не являются его выполнением.
|
||||
- **Current mechanism:** вручную исполнимый script сравнивает строку `start:prod`, проверяет наличие `dist/src/main.js` и импортирует compiled Prisma client. Npm script, workflow, Docker RUN/CMD и активный README его не вызывают.
|
||||
- **Impact / actual cost:** небольшой orphan file и специальные lint/test references; automated runtime protection не подтверждена. **Impact unmeasured**; потеря времени/CI не заявляется.
|
||||
- **Why now:** complete script inventory выявляет неопределённое ownership; это позволяет убрать лишний навигационный путь, если manual/external caller отсутствует.
|
||||
- **Minimum change:** сначала спросить release/runtime owner об external pipeline/manual use. При отрицательном ответе удалить файл и исключительно связанные config/test references; не добавлять новый alias только ради оправдания существования файла.
|
||||
- **Guarantees at risk:** проверка literal command string исчезнет; actual packaging/client loading частично защищает container smoke, но это другая boundary и другой trigger.
|
||||
- **Counterarguments / trade-offs:** файл может быть полезной дешёвой локальной проверкой build без Docker. Отсутствие repo references не исключает ручной запуск или external automation.
|
||||
- **Verification or rollback:** подтвердить отсутствие external/manual caller; сравнить защищаемые ошибки с existing image smoke и build. Если нужная local гарантия отсутствует, KEEP и документировать действующий caller. Rollback — восстановить 8-строчный file и его lint entries.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** TOOL-004 — общий вопрос ownership, не общая реализация.
|
||||
|
||||
### TOOL-004 — Publication recovery regression не подключён к найденному графу команд
|
||||
|
||||
- **Decision:** INVESTIGATE.
|
||||
- **Severity:** P3.
|
||||
- **Confidence:** Medium.
|
||||
- **Evidence:** `frontend/scripts/check-image-publication.cjs:15–29,31–47,49–87`; `frontend/.gitea/workflows/image.yml:46–62`; `frontend/.gitea/workflows/verify.yml:63,88–92`; `frontend/package.json:21,26`; `frontend/vitest.config.ts:26–64`; caller search по active docs/configs/scripts.
|
||||
- **Claim:** нет найденного локального исполняющего consumer, хотя test защищает реальную recovery-семантику публикации.
|
||||
- **Current mechanism:** Node test извлекает конкретный shell block из YAML по имени/indentation и запускает его с temporary fake Docker/npm. Проверяет failed first pull, успешный retry по тому же digest, label/smoke failure и один build.
|
||||
- **Impact / actual cost:** наличие test file само по себе не даёт regression gate. Его связь с workflow также требует поддержки при rename/reindentation шага. **Impact unmeasured**; actual CI failure/churn не установлен.
|
||||
- **Why now:** `test:lint` запускает один другой file; application Vitest projects не включают этот `.cjs` script; workflows не вызывают его напрямую. Нужна ясная responsibility перед cleanup.
|
||||
- **Minimum change:** установить, используется ли test manually/external pipeline. Если recovery-проверка нужна, закрепить существующий entrypoint у image-tooling owner; выбор trigger отдать audit 11. Если этот сценарий намеренно больше не нужен, удалять test только вместе с решением о гарантии.
|
||||
- **Guarantees at risk:** без равносильной regression можно потерять safe post-push resume/digest-bound smoke. Реальную immutable publication и label/digest checks не удалять.
|
||||
- **Counterarguments / trade-offs:** standalone manual test вполне допустим; широкий `verify` не обязан исполнять все diagnostic tools. String extraction тестирует actual block, но чувствительна к структуре YAML; переход на новый custom YAML framework не оправдан.
|
||||
- **Verification or rollback:** дешёвая safe проверка — текущий `node --test scripts/check-image-publication.cjs` после просмотра implementation, в synthetic окружении; в этом аудите не запускалась. Подтвердить manual ownership/trace перед удалением. Rollback — восстановить file/caller; registry не изменять.
|
||||
- **Effort:** S.
|
||||
- **Related finding IDs:** TOOL-003 — общий вопрос ownership.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Объект | Почему / сохранённая гарантия |
|
||||
| --- | --- | --- |
|
||||
| DELETE | Безусловных кандидатов нет | Ни один orphan не доказан dead с учётом manual/external use |
|
||||
| SIMPLIFY | F1 `check-openapi.mjs` implementation, TOOL-001 | Удалить файл после переноса alias на native `--check`; freshness и read-only output сохраняются |
|
||||
| FIX | Plain npx в pre-commit, TOOL-002 | Explicit refusal to install; staged secret scan и auto-fix остаются |
|
||||
| KEEP | Stable npm delegation aliases / workspace builds | Это interface commands, не 64 отдельных bespoke tools |
|
||||
| KEEP | F2/B1/S1/D1 release guards | Version equality не заменяется `requireCommits`; specs/docs porcelain дополнительно видит untracked |
|
||||
| KEEP | B3/B5 SQLite isolation/guard | Не допускает migrations/tests на inherited пользовательском DATABASE_URL |
|
||||
| KEEP | B4 runner regression | Manual proof process isolation/cleanup; не включён в обычный CI, стоимость не измерена |
|
||||
| KEEP | B8/B9/B10 image/durability/crash runners | Native modules/assets, durable publication, exact financial/replay/rollback guarantees |
|
||||
| KEEP | B2 и backend offline/runtime schema suite | Offline artifact producer + отдельный runtime parity proof |
|
||||
| KEEP | F3/B6 ESLint config regressions | Synthetic negative cases проверяют реальные scopes и formatter behavior |
|
||||
| KEEP | F4 Gitleaks staged/redaction regression | History scan не доказывает index-only behavior и downstream stop |
|
||||
| KEEP | SHA-256 fingerprints, CAS, cursor HMAC, digest/checksum assertions | Business/security/artifact invariants; native hashing уже используется |
|
||||
| KEEP | D2 one-line rule adapter / native depcruise configs | Уже минимальная delegation/config integration |
|
||||
| INVESTIGATE | B7 runtime-check, TOOL-003 | Possible unused; first verify external/manual ownership |
|
||||
| INVESTIGATE | F5 publication regression, TOOL-004 | Сохранить защиту recovery, пока не принято решение о consumer |
|
||||
| INVESTIGATE | F6 readability/JSX style policy | Нет подтверждённой native semantic equivalence или измеренной цены false positives |
|
||||
| INVESTIGATE | Prisma process-local RUST_LOG shim | Historical failure описан, current-version reproduction отсутствует; не удалять по факту обновления Prisma |
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| Изменение | Ожидаемый класс экономии | Что действительно измерено | Обязательная граница безопасности |
|
||||
| --- | --- | --- | --- |
|
||||
| TOOL-001 | Maintenance, cognitive load; меньше custom process/temp orchestration | Read tool показал 45 физических строк helper, включая imports/blanks; native success/failure выполнены. Runtime, CI minutes и agent tokens — **not measured** | Не заменять check на mutating generate; не обращаться к backend checkout/endpoint |
|
||||
| TOOL-002 | Предотвращение ненужной network/install работы и неоднозначного error recovery | **not measured**; подтверждена command capability, не частота fallback | Сохранить порядок scan → intended format → deps и failure propagation |
|
||||
| TOOL-003 | Potential cleanup одного 8-строчного файла и orphan references | File size из текущего чтения; benefit/use frequency — **not measured** | Manual/external use должен быть подтверждён до DELETE |
|
||||
| TOOL-004 | Ясность ownership / сохранение полезного test | **not measured**; test execution сейчас UNVERIFIED | Не терять immutable post-push recovery regression |
|
||||
| Protected runners | Избежание повторного создания inferior wrappers/checks | **not measured** | DB isolation, money/replay invariants, migration history и process shutdown остаются |
|
||||
|
||||
Complexity-pass в формате установленного skill:
|
||||
|
||||
- `native: убрать custom temp-generation checker; заменить installed openapi-typescript --check, сохранив npm alias. [frontend/scripts/check-openapi.mjs]`
|
||||
- **net: -45 lines, -0 deps possible.** Это потенциал удаления одного текущего 45-строчного helper, не measured net diff после сопутствующей правки docs/test fixture и не оценка ускорения. Условные orphan deletions в число не включены.
|
||||
|
||||
### Candidate Removal Plan
|
||||
|
||||
| Candidate | Минимальная безопасная последовательность | Proof before removal | Rollback |
|
||||
| --- | --- | --- | --- |
|
||||
| F1 helper — подтверждённый implementation removal | Перенести local alias на native check; поправить synthetic ESLint path и описание; удалить helper | Success + deliberate mismatch + unchanged output + existing lint config test | Восстановить file и alias; generated artifact не менять |
|
||||
| B7 runtime-check — условный | Подтвердить manual/external ownership; при отсутствии удалить file и только exclusive references | Existing build/image consumers сохраняются; определить нужность literal start command assertion | Восстановить file/config references |
|
||||
| F5 publication regression — удаление сейчас не рекомендовано | Сначала решить, является ли recovery scenario сохраняемой гарантией; при необходимости оставить существующий test у owner | Перед потерей гарантии нужен равносильный proof или явное решение о её прекращении | Восстановить test/caller; не перепубликовывать image |
|
||||
|
||||
Не предлагать общий release-tooling repo, новый YAML/parser framework, новую dependency для трёхстрочного hook или custom fingerprint framework.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **TOOL-002:** закрыть install fallback pre-commit одной explicit no-install опцией. Low effort; полезно даже без доказанной частоты fallback.
|
||||
2. **TOOL-001:** перенести `openapi:check` на installed native `--check`, удалить wrapper implementation; сохранить имя команды и обновить две связанные references.
|
||||
|
||||
### Решения с ownership / тестовым подтверждением
|
||||
|
||||
1. **TOOL-003:** уточнить manual/external consumers runtime-check; DELETE только при подтверждённом отсутствии потребности.
|
||||
2. **TOOL-004:** уточнить owner/consumer существующей recovery regression; по умолчанию сохранить. Trigger выбирать в CI-аудите, не расширять canonical verify автоматически.
|
||||
|
||||
Ни одно подтверждённое quick win не требует ADR. ADR имеет смысл только если будет изменяться публичная release/recovery или DB-safety гарантия, а не из-за удаления wrapper file. Массовое переписывание runners и общие tooling abstractions не рекомендованы.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **Audit 11 — CI/CD:** `frontend/scripts/check-image-publication.cjs`, `frontend/.gitea/workflows/image.yml`, `backend/.gitea/workflows/image.yml:45–55` — owner/trigger recovery test и различающаяся политика повторного запуска после post-push failure; здесь registry-поведение не воспроизводилось.
|
||||
- **Audit 13 — security/integrity:** hash/HMAC/CAS consumers из registry — сохранить guarantees; отдельно оценивать binding/key policy, не трактовать наличие SHA как over-engineering.
|
||||
- **Audit 14 — agent workflow/conventions:** `frontend/eslint-rules.mjs:63–98`, `frontend/eslint.config.mjs:270–282` — нужность общего запрета local component handlers/helpers и реальный churn должны оцениваться по policy/consumer, не по числу lint rules.
|
||||
- **Граница targets:** отдельный `deploy/deploy.sh` и `deploy/tests/` требуют явного дополнительного tooling target; их анализ не входит в инвентарь четырёх targets этого запуска.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Текущий статус | Самый дешёвый способ проверить |
|
||||
| --- | --- | --- |
|
||||
| Есть ли внешний/manual caller runtime-check? | UNVERIFIED | Ответ runtime owner / поиск в его external pipeline configuration. Без caller и потребности DELETE; иначе KEEP |
|
||||
| Выполняется ли publication recovery test вручную и нужна ли эта гарантия? | UNVERIFIED | Ответ image owner и один existing synthetic Node test. Затем решение о consumer, не о новом framework |
|
||||
| Происходил ли npx install fallback во время commit? | UNVERIFIED; capability подтверждена | Не требуется для однострочного FIX. Если нужна оценка частоты — только имеющиеся redacted logs; не удалять пакеты в checkout |
|
||||
| Можно ли заменить JSX-spacing native rule без text/layout изменений? | UNVERIFIED, code semantics различаются | На существующих representative TSX/examples сравнить diagnostics и in-memory fixes; не делать массовый autofix для исследования |
|
||||
| Нужен ли RUST_LOG workaround на Prisma 7.10.0? | UNVERIFIED | В отдельной разрешённой тестовой сессии fresh temporary DB migration с warn/error и default env; пользовательскую DB не использовать |
|
||||
|
||||
Отчёт не подтверждает доступность real registry/runner, correctness всех financial/hash algorithms, полноту HTTP contract behavior или actual full-suite PASS. Эти ограничения не мешают подтверждённому native-wrapper cleanup.
|
||||
|
||||
## Машинно-считываемая таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| TOOL-001 | SIMPLIFY | P3 | High | frontend | scripts/check-openapi.mjs; package.json; scripts/test-eslint.mjs; .ai/conventions/data.md | Native --check preserves freshness while removing one custom wrapper | S | — |
|
||||
| TOOL-002 | FIX | P3 | High | frontend | .husky/pre-commit | Plain npx permits unnecessary installation when the local lint tool is absent; occurrence unverified | S | — |
|
||||
| TOOL-003 | INVESTIGATE | P3 | Medium | backend | test/runtime-check.cjs; eslint.config.mjs; test/eslint-config.cjs | No executing local caller found; manual/external use must be verified before removal | S | TOOL-004 |
|
||||
| TOOL-004 | INVESTIGATE | P3 | Medium | frontend | scripts/check-image-publication.cjs; .gitea/workflows/image.yml | Useful publication recovery regression has no discovered executing consumer | S | TOOL-003 |
|
||||
@@ -0,0 +1,490 @@
|
||||
# Аудит 11 — Validation & CI/CD Efficiency
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID/name:** 11 / Validation & CI/CD Efficiency.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Режим:** read-only для application/specs/docs repos; единственная запись — этот отчёт в родительском workspace.
|
||||
- **Область:** execution DAG проверок, triggers, независимые CI guarantees, повторная подготовка, staged hooks, генерация контрактов и граница image publication. Алгоритмы самописных инструментов, assertions тестов, dependency security и поведение агента не аудировались.
|
||||
- **Skill:** установленный `ponytail-audit` прочитан через Skill tool и применён к complexity-pass. Его ограничения сохранены: correctness findings ниже — результат протокола аудита 11, а не вывод skill. Дополнительно прочитан `verification-before-completion` для контроля итогового артефакта.
|
||||
- **Предыдущий аудит:** `audit-results/technical-audit-2026-10-11.md` прочитан как реестр гипотез. Его измерения dependency graph, совпадения OpenAPI и выводы о тестах не использованы как актуальное доказательство.
|
||||
|
||||
Workspace: `/Users/ksv741/Project/vibe/WealthLedger`. Это родительская папка самостоятельных Git-репозиториев:
|
||||
|
||||
| Repo | Найденный путь относительно workspace | HEAD SHA | Ветка | Начальный `git status --short` |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | `codex/fix-stock-screener-race` | clean |
|
||||
| backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | `codex/add-stock-screener` | clean |
|
||||
| specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | `codex/add-stock-screener` | clean |
|
||||
| docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | `codex/add-stock-screener` | clean |
|
||||
| workspace root, вспомогательная область | `./` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | `release/0.2.1` | clean |
|
||||
|
||||
Также обнаружен отдельный `deploy/.git`; его исходники и pipeline не исследовались. Downstream deployment описан только по доступным root Compose и `docs/architecture/deployment.md`. Наличие локального checkout не доказывает production state.
|
||||
|
||||
### Что выполнялось
|
||||
|
||||
1. Статическое чтение repo instructions, manifests, workflows, hooks, compiler/test/build configs и вызываемых entry points; файловый inventory через Read/Glob/Grep.
|
||||
2. В каждом из четырёх repos и root: `git status --short --branch`, `git rev-parse HEAD`.
|
||||
3. В четырёх repos: read-only `git config --get core.hooksPath`. Frontend: `.husky/_`; specs: `.githooks`; backend/docs: значение отсутствует, команда вернула 1 без вывода. Это отсутствие настройки, а не ошибка проверки приложения.
|
||||
4. В specs: `git diff --check && git diff --stat && git diff --cached --stat` — exit 0, вывода нет; проверены пустые локальные diffs, не PR content.
|
||||
5. Через Context7 прочитана актуальная документация Gitea о PR activity types и head ref: [Actions FAQ](https://docs.gitea.com/usage/actions/faq), [webhook payload](https://docs.gitea.com/usage/repository/webhooks). Внешний источник подтверждает поддержку `edited`; версия и настройки реального Gitea не запрашивались.
|
||||
6. После записи отчёта: повторный read-only контроль Git status/HEAD всех пяти областей — SHA не изменились, working trees clean. `git diff --check` root — без diagnostics. No-index whitespace-check отчёта относительно `/dev/null` — без diagnostics; exit 1 обозначает наличие нового содержимого относительно пустого файла. Root `git status --short --ignored -- <report>` показывает игнорируемый `audit-results/`, согласно allowlist; отчёт не добавлялся в tracking.
|
||||
|
||||
**Не выполнялось:** npm/Node/OpenSpec команды, install, lifecycle hooks, tests, lint, build, full verify, Prisma/OpenAPI codegen, Docker, migrations, обращения к брокерским/банковским API, npm audit, чтение secrets, commit/PR, изменение source/config/lockfiles/generated artifacts. CI runs/logs и branch protection settings не запрашивались. Тайминги отсутствуют; все оценки экономии — **not measured**.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт / FIX:** `specs verify` в CI проверяет разницу working tree с index, а не проверяемые commits. В чистом checkout это пустая проверка, хотя её статус является задуманным required check. Самый дешёвый полезный gate — whitespace-check явного диапазона commits без Node installation в этом job. `VERIFY-001`.
|
||||
2. **Факт / риск:** четыре repos исключают PR `edited` из triggers commitlint. Заголовок можно изменить после зелёного статуса без нового запуска; frontend/backend README обещают обратное. `VERIFY-002`.
|
||||
3. **Факт / SIMPLIFY:** backend non-scheduled verify job содержит **3 явных Prisma generation и 2 Nest build**. При этом standalone `verify` генерирует client слишком поздно для чистого checkout. Нужен один owner подготовки до потребителей типов и reuse той же сборки в e2e, а не механическое удаление первого generate. `VERIFY-003`.
|
||||
4. **Факт / SIMPLIFY:** frontend pre-commit после staged lint каждый раз собирает два private packages и сканирует весь dependency graph, даже для Markdown-only commit. Полный граф проверяется также в final verify и CI. Обязательные security/staged lint можно сохранить, а full graph оставить composed gates. `VERIFY-004`.
|
||||
5. **KEEP:** CI уже избегает дублирующего feature-branch push-run, имеет fail-fast цепочку `commits → security → verify`, отмену устаревших ревизий и вынесенный тяжёлый performance benchmark. Unit/SQLite, JSDOM/Chromium и два dependency graphs имеют разные гарантии; объединять их по похожим именам нельзя.
|
||||
6. **UNVERIFIED:** фактические CI minutes, npm/browser/build cache hits, версия Gitea, действующие required checks и защита устаревшего PR head от нового base не проверены. Это вопросы для короткого просмотра существующего run и настроек, а не основание отключать gates.
|
||||
|
||||
**Итог:** четыре подтверждённых P2 findings, без подтверждённых P0/P1. Основная экономия достигается сокращением повторной подготовки и правильным input scope дешёвых gates; расширение количества валидаторов не требуется.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1 Изученные объекты и метод подсчёта
|
||||
|
||||
| Объект | Охват и метод | Результат |
|
||||
| --- | --- | --- |
|
||||
| CI definitions | Полное чтение всех файлов в `.gitea/workflows/` четырёх repos и root | 11 workflow files, 15 job definitions; сумма: frontend 2/4, backend 2/4, specs 3/3, docs 3/3, root 1/1 |
|
||||
| CI platforms | Все перечисленные job definitions и `.nvmrc` | Один label `ubuntu-latest`, нет OS/Node matrix; все `.nvmrc` — `24.21.0` |
|
||||
| Local commands | Пять root manifests, три frontend workspace manifests | Раскрыты `verify`, standalone checks, build, generation, release entry points и root lifecycle |
|
||||
| Git hooks | Tracked hook files, actual hooksPath, backend/docs `.git/hooks`, Husky launcher | Frontend: pre-commit + commit-msg; specs: commit-msg; backend/docs: только `.sample`; активных pre-push checks не найдено |
|
||||
| Backend e2e selection | Glob `backend/test/*.e2e-spec.ts` + `test/sqlite.cjs:7–17` | 20 suite files; `--all` выбирает 19 functional suites, исключая один performance suite; это число файлов, не assertions |
|
||||
| Browser selection | Glob `*.browser.test.tsx` в frontend `src`/`packages` + `vitest.config.ts:36–64` | 2 файла, каждый включён для narrow/wide Chromium instances: 4 configured file-instance combinations без фактического запуска |
|
||||
| Contracts | Backend generator + runtime integration suite; frontend check/generate | Прослежены источник, временный output, byte comparison, freshness и consumers |
|
||||
| Downstream artifacts | Dockerfiles, image workflows, coverage/Sonar configs, docs deployment | Build provenance boundary и consumers покрыты статически; registry/deploy execution не проверены |
|
||||
|
||||
Counts относятся только к найденным локальным checkout, не к server-wide jobs/runners. Явные повторы команд считались разворачиванием manifest chains и workflow steps на успешном пути; lifecycle сторонних packages и cache-hit effects не выдаются за измеренную длительность.
|
||||
|
||||
Из installed manifests отдельно проверены `backend/node_modules/@prisma/client/package.json:264–275` и `backend/node_modules/prisma/package.json:194–205`: у этой версии client нет postinstall generation, Prisma имеет preinstall. Это поддерживает гипотезу cold-checkout failure standalone verify; установленное дерево не заменяет воспроизведение fresh install.
|
||||
|
||||
### 3.2 Исследовательский порядок и исключённые гипотезы
|
||||
|
||||
1. Inventory entry points → выбор рисков stale/no-op proof и повторной подготовки.
|
||||
2. Tracing workflow → package script → runner/generator/build outputs → consumer.
|
||||
3. Проверка альтернатив: отдельный front generate нужен до типов; standalone wrappers обеспечивают свежесть packages; graph modes отличаются; OpenAPI delivery явно ручная; image publication не равна server rollout.
|
||||
4. Сформированы четыре findings; без искусственного расширения до всех возможных оптимизаций.
|
||||
|
||||
Не аудировались assertions и качество coverage, алгоритмы custom scripts, advisories/overrides, OpenSpec schema/lifecycle design, глобальные editor/agent hooks и отдельный deploy repo. Исторические archives не использованы как proof текущих triggers.
|
||||
|
||||
## 4. Check Execution DAG
|
||||
|
||||
Ниже **фактическая конфигурация**. Обязательное локальное действие из conventions отмечено отдельно от автоматического trigger.
|
||||
|
||||
### 4.1 Edit/save и ручная проверка
|
||||
|
||||
```text
|
||||
developer / agent edits
|
||||
├─ frontend npm run dev [только если процесс запущен]
|
||||
│ └─ build:packages → Vite/HMR на изменениях
|
||||
├─ frontend npm run dev:catalog [только если процесс запущен]
|
||||
│ └─ build:packages → Storybook dev :6006
|
||||
├─ frontend npm test [только если вручную выбран watch]
|
||||
│ └─ build:packages → Vitest watch
|
||||
├─ backend start:dev/debug [только если запущен]
|
||||
│ └─ Nest watch → dist-dev; это не full test gate
|
||||
└─ manual checks [решение автора]
|
||||
├─ frontend targeted test / typecheck / lint / deps
|
||||
│ └─ каждый standalone wrapper сначала build:packages
|
||||
├─ backend targeted Jest / typecheck / isolated SQLite
|
||||
└─ feature complete [локальное требование engineering:60–62]
|
||||
├─ npm ci → root/dependency lifecycle
|
||||
├─ frontend verify
|
||||
└─ backend verify + risk-specific e2e/contract/durability
|
||||
```
|
||||
|
||||
Автоматического full verify на каждый save в просмотренных application configs нет. Vite transform/HMR не доказывает typecheck, test coverage или production bundling. Backend dev compilation не заменяет configured HTTP/DB suites. Все global editor integrations остаются вне этого inventory.
|
||||
|
||||
### 4.2 Раскрытые gates
|
||||
|
||||
```text
|
||||
frontend verify [всё через &&, stop на первой ошибке]
|
||||
build:packages
|
||||
├─ icons: vite build → tsc declarations
|
||||
└─ ui: vite build → tsc declarations
|
||||
tokens: JSON export, build script отсутствует
|
||||
→ openapi:check [temp generation + byte compare types]
|
||||
→ tsc -b --noEmit
|
||||
→ eslint . --max-warnings=0
|
||||
→ test:lint [node:test]
|
||||
→ vitest run --coverage [JSDOM + Chromium narrow/wide]
|
||||
→ knip
|
||||
→ depcruise base
|
||||
→ depcruise Storybook, reachability/runtime graph
|
||||
→ tsc -b
|
||||
→ vite build [app/PWA dist]
|
||||
→ storybook build [storybook-static]
|
||||
|
||||
backend verify [всё через &&]
|
||||
lint → test:lint → typecheck:app → typecheck:test
|
||||
→ test:cov --runInBand [Jest в src]
|
||||
→ build [prisma generate → nest build]
|
||||
|
||||
backend test:e2e
|
||||
build [prisma generate → nest build]
|
||||
→ sqlite.cjs --all
|
||||
для каждой functional suite последовательно:
|
||||
temp DB → штатные migrate deploy → Jest --runInBand → cleanup
|
||||
|
||||
backend test:openapi-contract
|
||||
build → sqlite.cjs openapi-contract.e2e-spec.ts
|
||||
configured app schema ↔ generator from dist in temporary cwd
|
||||
|
||||
docs verify
|
||||
Markdown lint + relative links/anchors
|
||||
→ отдельный CHANGELOG lint profile → git diff --check
|
||||
|
||||
specs verify
|
||||
git diff --check [working tree ↔ index, не PR range]
|
||||
```
|
||||
|
||||
Sources: `frontend/package.json:9–38`, workspace manifests, `backend/package.json:11–38,123–138`, `backend/test/sqlite.cjs:7–17,57–95`, `docs/package.json:8–13`, `specs/package.json:8–14`.
|
||||
|
||||
### 4.3 Commit/push/PR
|
||||
|
||||
```text
|
||||
git commit frontend [actual hooksPath .husky/_]
|
||||
Husky sh -e
|
||||
→ staged Gitleaks, fail closed + redaction
|
||||
→ lint-staged → ESLint --fix [selected staged JS/TS]
|
||||
→ npm run deps → build:packages → full working-tree graph
|
||||
→ commit-msg → commitlint
|
||||
|
||||
git commit specs
|
||||
commit-msg → commitlint --strict
|
||||
|
||||
git commit backend/docs
|
||||
application hooks отсутствуют в просмотренном checkout
|
||||
|
||||
git push feature branch
|
||||
pre-push application checks отсутствуют
|
||||
├─ без открытого PR: verify workflow не запускается
|
||||
└─ с открытым PR: synchronize → PR workflow
|
||||
|
||||
PR opened / synchronize / reopened
|
||||
frontend/backend:
|
||||
commits [checkout head, npm ci --ignore-scripts, title + commit range]
|
||||
→ security [full-history Gitleaks + npm audit]
|
||||
→ verify [fresh npm ci; подготовка; full gates]
|
||||
→ Sonar только для develop push, не PR
|
||||
docs/specs:
|
||||
commits ∥ security ∥ verify [независимые workflows]
|
||||
root:
|
||||
commits, включая edited [только root PR]
|
||||
|
||||
PR edited
|
||||
root commits запускается
|
||||
четыре repo commitlint workflows не подписаны
|
||||
|
||||
push develop/main
|
||||
frontend/backend: commits skipped → security → verify
|
||||
docs/specs: security ∥ verify; commits отсутствует
|
||||
|
||||
daily schedule
|
||||
frontend/backend/specs/docs: security only
|
||||
```
|
||||
|
||||
Frontend/backend PR branch не имеет отдельного push-run: это уже полезная защита от двойного CI для одного feature push. Push после merge проверяет новое состояние интеграционной ветки, а не автоматически бесполезно повторяет PR trust boundary.
|
||||
|
||||
В Gitea PR ref указывает на **head**, не на synthetic merge commit — это подтверждено Actions FAQ. Отсутствие merge preview нельзя компенсировать предположением, что зелёный PR автоматически проверен с новым base. Actual stale-branch protection — `UNVERIFIED`.
|
||||
|
||||
### 4.4 Build/publication/deploy
|
||||
|
||||
```text
|
||||
release:prepare [manual, release/*]
|
||||
release-it guards + check-release
|
||||
→ version/lock/changelog commit; tag/push disabled
|
||||
manual release PR + protected merge + tag vX.Y.Z
|
||||
→ component image.yml
|
||||
version + HEAD/revision validation
|
||||
→ npm ci → canonical verify [backend также e2e]
|
||||
→ registry login → immutable tag absence check
|
||||
→ Docker production build/push
|
||||
→ pull exact digest → labels + container smoke
|
||||
→ cleanup registry session [always]
|
||||
→ explicit operator selection of component tags / deploy bundle
|
||||
→ rollout [отдельный repo, здесь не проверялся]
|
||||
```
|
||||
|
||||
Docker заново выполняет installation/build внутри packaging environment. Это другая гарантия: чистый Linux image с нужными native modules/assets, а не повтор host typecheck ради того же proof. Backend дополнительно имеет повтор генерации *внутри* Docker stages — `VERIFY-003`.
|
||||
|
||||
Container smoke сейчас идёт **после push**, поэтому его failure не отменяет publication. Это не automatic deploy: `docs/architecture/deployment.md:76–85,132–135` явно требует отдельно проверить artifact и выбрать bundle, запрещает удалять tag ради retry. Backend workflow отказывается повторно публиковать существующий tag; frontend умеет проверить существующий digest. Без доказательства опасного rollout это не отдельный дефект аудита; immutable guards и operator smoke — **KEEP**.
|
||||
|
||||
## 5. Guarantee-to-Check Matrix
|
||||
|
||||
Для всех rows **expected runtime = not measured**. Timeout job — верхняя граница ожидания, а не measured runtime. `R` = read-only для versioned inputs; `W` = writes outputs/index/config. Даже R-check может иметь временные cache files.
|
||||
|
||||
| Check | Guarantee | Trigger / input scope | Read/write behavior | Downstream consumer |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `npm ci` | Manifest/lock reproducible installation, не semantic proof | Conventions completion; CI install; все lockfile dependencies | W: node_modules; lifecycle, frontend Husky/specs hooks config | Все Node checks; Docker dependencies |
|
||||
| Frontend `build:packages` | JS/CSS/declarations для package exports актуальны | Standalone wrappers, verify, dev/catalog/build; icons/ui целиком | W: package dist | TS, ESLint resolver, Vitest, app/Storybook |
|
||||
| Frontend `openapi:check` | Committed `schema.d.ts` байтово соответствует генерации из принятого snapshot | Full verify; snapshot + generator version + generated file | R tracked; W private temp + cleanup | Frontend typed API client |
|
||||
| Backend `openapi:generate` | Artifact построен из текущего compiled backend | Manual delivery; current DTO/Swagger + dist | W: root ignored openapi.json; сначала build | Явно передаваемый consumer artifact |
|
||||
| Backend OpenAPI integration | Generator output from current build равен schema configured current app; generator не открывает сеть | Contract change; CI e2e --all | W private DB/temp JSON; не versioned snapshot | Публичный generator и HTTP contract |
|
||||
| Frontend `tsc -b`, с/без CLI `--noEmit` | Repo-wide strict TS configs и test/story/package compilation | typecheck, verify, app build; referenced projects | W buildinfo; app/node configs имеют noEmit | Локальный proof типов, bundle gates |
|
||||
| Backend typecheck app/test | Production roots и test compilation | verify / manual; app subset и весь src+test | R, incremental:false/noEmit:true | Unit/e2e compilation confidence |
|
||||
| Full ESLint + config tests | Configured rules, zero-warning policy, validation profile | verify/standalone; repo JS/TS | R для check; lint:fix отдельно W | Code review и next gates |
|
||||
| lint-staged | Lint selected staged JS/TS + fixes | Frontend commit | W: selected content/index, возможный stash | Commit contents; не full-repo proof |
|
||||
| Frontend Vitest coverage | Unit/transport/UI + actual layout checks; configured coverage floors | verify/coverage; full configured projects | W: coverage HTML/lcov и test artifacts | Merge gate, Sonar lcov |
|
||||
| Backend Jest coverage | Colocated src unit suite проходит | verify/test:cov; src specs | W: coverage; общего threshold нет | Merge gate, Sonar lcov |
|
||||
| SQLite functional runner | Configured HTTP/DB suites на isolated fresh migrations | test:e2e, CI; 19 выбранных suite files | W: temp DB only, cleanup | Authorization, transaction/CAS/replay/contract proof |
|
||||
| `test:sqlite` | Auth/cards subset, не все HTTP suites | Manual risk-based iteration; default 2 suite files | W isolated DB; требует заранее generated client | Быстрая DB regression feedback |
|
||||
| Base dependency-cruiser | Cycles, resolution, public API и layer boundaries | deps/verify/hook; весь src+packages, TS deps | R graph; wrapper W package build | Architecture gate |
|
||||
| Storybook dependency-cruiser | Runtime reachability stories/preview не доходит до data/auth/routes | deps:storybook/verify; graph + preview, tsPreCompilationDeps:false | R graph; wrapper W package build | Независимость UI catalog |
|
||||
| Knip | Unused exports/dependencies с repo entry point policy | verify/standalone; all workspaces | R analysis, wrapper W package build | Maintenance feedback |
|
||||
| Vite/Storybook builds | App/PWA и catalog действительно bundle | verify/build; разные roots/plugins | W dist/storybook-static | Docker app output / UI catalog |
|
||||
| Commitlint title + range | Заголовок и выбранные commits Conventional Commits | PR events; title + BASE/HEAD | R; job installation W | Required commits status; release history |
|
||||
| Gitleaks | Staged/history secret scan + redaction/fail closed | Frontend commit index; CI full available history, daily | R inputs; CI downloads scanner; temp hook test | Commit/merge security boundary |
|
||||
| npm audit | Registry advisory policy проходит для выбранных trees/levels | PR/push/daily security | Network read + возможные local logs; lock unchanged | Required security status |
|
||||
| Docs Markdown | Syntax + local Markdown links/anchors, включая новые файлы | verify/CI; all Markdown + отдельный CHANGELOG | R | Docs consistency; не Mermaid/semantic proof |
|
||||
| Specs/docs `git diff --check` | Whitespace errors в незакоммиченном working-tree diff | local verify, также current CI commands | R; untracked/index-only/committed changes не охвачены | Локальная проверка; в clean CI inputs пусты |
|
||||
| Docker production build + smoke | Packaging, native runtime/assets/migrations и startup проходят | Release tag; image environment и pulled digest | W image/registry/disposable volumes | Explicit deployment selection |
|
||||
| Sonar Quality Gate | Remote analyzer принял source и existing unit lcov | Только develop push после gates | W remote analysis | Develop push status, не PR proof |
|
||||
| Durability / performance / runner regression | Migration/restart/backup proof либо load/runner behavior | Manual risk checks; не ordinary verify | W disposable outputs/DB; regression многократно запускает e2e | Schema-change proof / dedicated investigation |
|
||||
|
||||
### OpenAPI: четыре разных утверждения
|
||||
|
||||
1. **Backend file equals frontend snapshot:** сравнение двух сохранённых JSON доказало бы лишь их текущее совпадение. В этом аудите оно не выполнялось.
|
||||
2. **Backend generator freshness:** `test/openapi-contract.e2e-spec.ts:108–158` вызывает generator from fresh dist в temporary cwd и сравнивает его JSON с `createOpenApiDocument(app)` current source/configured app. Это реально покрывается CI e2e.
|
||||
3. **Frontend type freshness:** `scripts/check-openapi.mjs:10–38` генерирует из frontend-local accepted snapshot и сравнивает bytes `schema.d.ts`; backend freshness из этого не следует.
|
||||
4. **Release compatibility / latest delivery:** предыдущий release и предоставленный frontend artifact — другие inputs. Backend не имеет versioned JSON baseline (`backend/.gitignore:69–70`), delivery описана явно (`backend/README.md:218–228`, `frontend/README.md:201–204`). Эти guarantees не были заявлены existing check и здесь остаются unverified.
|
||||
|
||||
**KEEP:** independent producer/consumer checks и offline generator guard. Не добавлять обязательный sibling checkout или второй editable contract source.
|
||||
|
||||
## 6. Duplicate Execution Matrix
|
||||
|
||||
### 6.1 Check × triggering context
|
||||
|
||||
Это configured occurrences на успешном пути, **не частота реальных действий**. `F` = один frontend full verify; `B` = один backend full verify. Host и Docker считаются раздельно.
|
||||
|
||||
| Check | Frontend targeted wrapper | F | Frontend pre-commit | Frontend CI verify job | Backend targeted contract | B | Backend CI verify job: pre-step + B + e2e | Release image job |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| Frontend package build | 1 для test/type/lint/deps/knip | 1 | 1 всегда после lint-staged | 1 | — | — | — | 1 host F + 1 Docker app build при cold build |
|
||||
| Frontend repo tsc build mode | 0 или 1, по wrapper | 2 invocations | 0 | 2 | — | — | — | F + Docker type/build path |
|
||||
| Frontend base depcruise | 0 или 1 | 1 | 1 | 1 | — | — | — | 1 host F |
|
||||
| Storybook graph | 0 или 1 | 1 | 0 | 1 | — | — | — | 1 host F |
|
||||
| Vitest target / full coverage | Targeted subset | 1 full | 0 | 1 full | — | — | — | 1 host full |
|
||||
| Prisma generation, explicit | — | — | — | — | 1 | 1, поздно | 3 | 3 host + 2 Docker stage path при cold backend image build |
|
||||
| Nest build | — | — | — | — | 1 | 1 | 2 | 2 host + 1 Docker |
|
||||
| Backend unit coverage | — | — | — | — | 0 | 1 | 1 | 1 host |
|
||||
| OpenAPI integration | — | — | — | — | 1 suite | 0 | 1 suite внутри --all | 1 suite внутри --all |
|
||||
| Backend functional SQLite suites | — | — | — | — | 1 suite | 0 | 19 suite files | 19 host suite files |
|
||||
|
||||
Frontend два tsc invocations **не равны двум полным recompilations**: referenced configs уже `noEmit`, используются buildinfo. Ускорение от удаления второго не измерено. Backend app/test typecheck имеют перекрытие src, но разные program roots; удаление app-only proof без проверки ambient test effects не рекомендовано.
|
||||
|
||||
### 6.2 Матрица трёх событий
|
||||
|
||||
| Событие | Возможный локальный путь | Commit | Push / PR | Где повтор действительно лишний / где оправдан |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| (a) Одна UI правка в frontend | HMR, optional `test:run -- <path>` → по completion policy npm ci + F | Staged Gitleaks + ESLint fixes + full deps с package build | Feature push: только PR synchronize, если PR открыт; CI fresh install + F | Targeted test и F дают разные scope/coverage; CI — independent boundary. Hook повторяет package build/base graph после F; для Markdown этот же hook не имеет relevant graph input |
|
||||
| (b) Backend contract change | Targeted HTTP/contract; `test:openapi-contract` уже build+suite → B + relevant e2e; generator delivery отдельно | Backend application hook нет | PR: commits → security → pre-generate + B + build/e2e | Отдельный contract command и --all могут повторить ту же suite; в одной неизменённой local revision повторный targeted run не нужен. В CI 3 generate/2 builds обслуживают одну revision и environment |
|
||||
| (c) git push / PR | Push сам не вызывает local verify | pre-push checks нет | Feature без PR: pipeline нет. PR open/update: full gates. Merge push develop/main: integration gates. Release tag: image pipeline | PR/branch/tag stages имеют отдельные inputs и trust boundaries. Не удалять tag verification без same-SHA/lock/environment gated proof. Изменение только PR title сейчас не вызывает новый commits check |
|
||||
|
||||
UI изменение не запускает backend/specs/docs checks автоматически: это независимые repositories. Backend artifact change требует проверки frontend только при явном изменении принятого frontend input, а не обязательного просмотра соседнего repo для любой backend правки.
|
||||
|
||||
## 7. Findings
|
||||
|
||||
### VERIFY-001 — Specs CI verify не проверяет проверяемый commit range
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `specs/package.json:8–14`; `specs/.gitea/workflows/verify.yml:13–22`; `specs/README.md:33–35,52–55,68–70`. Для сравнения настоящее content-check в docs: `docs/package.json:8–10`, `docs/.gitea/workflows/verify.yml:23–25`.
|
||||
- **Claim:** required-by-intent specs `verify` в clean CI checkout не проверяет whitespace committed PR/push content. Node setup/install обслуживает фактически пустой worktree diff.
|
||||
- **Current mechanism:** workflow checkout → setup-node → npm ci → `git diff --check`; перед этой командой нет изменения tracked content и не передаются BASE/HEAD. `prepare` меняет hooksPath, не рабочие Markdown inputs.
|
||||
- **Consumer:** PR required `verify` из README; локальный автор использует тот же script. Локально команда полезна для unstaged tracked diff, но не охватывает уже staged changes и новые untracked files.
|
||||
- **Impact / actual cost:** ошибки whitespace в committed change проходят этот job; ложная уверенность в проверке diff. Отдельные setup/install steps не дают content guarantee. **Impact unmeasured**: реальный пропущенный PR и CI minutes не наблюдались.
|
||||
- **Why now:** это можно исправить существующим Git check, не вводя новую validation framework и не изменяя ответственность specs.
|
||||
- **Minimum change:** оставить локальный script локальной проверкой либо явно охватить staged diff; в CI выбрать содержательный commit range и вызвать Git напрямую. Для PR обеспечить наличие base/head commits, проверить merge-base → head; для push — before → after, с явно определённым fallback для creation/zero SHA. Только после этого убрать setup-node/npm ci из *verify job*; commits/security остаются прежними.
|
||||
- **Guarantees at risk:** корректность range и presence of history. Shallow checkout без нужного base даёт неполную проверку; просто удалить job при unchanged required-check settings нельзя.
|
||||
- **Counterarguments / trade-offs:** specs README обещает лишь diff, не native OpenSpec validation. Finding не требует запускать schema/semantic validation на каждый push. Docs verify не является no-op: его Markdown lint действительно читает содержимое, хотя последующий worktree diff там также пустой.
|
||||
- **Verification or rollback:** в будущем PR с trailing whitespace в committed Markdown должен fail, исправленный — pass; проверить staged/local и first-push fallback отдельно. Достаточен disposable test branch/repo, application suite не нужна. До proof оставить существующий required check и затем сохранить его имя. Rollback — вернуть workflow, не менять Git history.
|
||||
- **Related finding IDs:** VERIFY-002; cross-audit 15 только по самостоятельной native OpenSpec policy.
|
||||
|
||||
### VERIFY-002 — Commitlint не перезапускается при изменении заголовка PR
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `frontend/.gitea/workflows/verify.yml:3–7,17–37`; `backend/.gitea/workflows/verify.yml:3–7,17–37`; `docs/.gitea/workflows/commits.yml:3–5,23–30`; `specs/.gitea/workflows/commits.yml:3–5,23–30`; обещание — `frontend/README.md:116–120`, `backend/README.md:80–85`; работающий root precedent — `.gitea/workflows/commits.yml:3–5,19–26`.
|
||||
- **Claim:** все четыре repo workflows фильтруют `opened/synchronize/reopened` и не обрабатывают `edited`; title input может измениться без rerun validation, при том же HEAD.
|
||||
- **Current mechanism:** job читает event PR_TITLE и проверяет его вместе с commit range только на перечисленных events. Local commit-msg проверяет commit, а не будущий PR title, и не компенсирует отсутствие trigger.
|
||||
- **Consumer:** required `commits` status, Conventional Commit policy для PR/release history. Gitea FAQ подтверждает `edited` activity; отсутствие этого type — конфигурационное, а не отсутствие platform capability.
|
||||
- **Impact / actual cost:** green status относится к прежнему title; после invalid rename policy proof становится stale. Фактический merge с таким title не наблюдался; **Impact unmeasured**.
|
||||
- **Why now:** несоответствие дешёвого check собственному input trigger устраняется малой правкой; не нужны новые rules.
|
||||
- **Minimum change:** обеспечить commits/title check на `edited`; docs/specs могут добавить type в отдельный commits workflow. Для frontend/backend сохранить selective dispatch: metadata-only event должен обновить commits proof без нового Chromium/Jest/build run. Не добавлять `edited` в full workflow без условия, если это просто умножит дорогие gates.
|
||||
- **Guarantees at risk:** текущие names required checks и отношение skipped jobs к имеющимся success statuses. При разделении workflow нельзя случайно сделать старый обязательный context недостижимым или заменить failed verification на skipped success.
|
||||
- **Counterarguments / trade-offs:** title lint — выбранное самим проектом правило, не runtime-security boundary. Его можно отдельно пересмотреть как policy, но workflow сейчас обещает его enforce. Содержание PR body тоже может давать `edited`; желательно проверять title-change payload, если платформа предоставляет нужные fields, без сложного custom classifier.
|
||||
- **Verification or rollback:** на existing successful PR изменить только title на invalid, убедиться в новом failed commits check; вернуть valid title и получить success без нового verify run. Проверить branch protection contexts и поведение skipped statuses на фактической Gitea. Rollback — trigger/job changes, contexts восстанавливаются согласованно.
|
||||
- **Related finding IDs:** VERIFY-001; остальные guarantees independent.
|
||||
|
||||
### VERIFY-003 — Backend preparation повторяется и не принадлежит standalone verify целиком
|
||||
|
||||
- **Decision = SIMPLIFY; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `backend/.gitea/workflows/verify.yml:83–89`; `backend/.gitea/workflows/image.yml:30–33`; `backend/package.json:12–13,22–34`; `backend/.gitignore:7–8`; `backend/prisma/schema.prisma:1–5`; `backend/src/prisma/prisma.service.ts:1–5`; `backend/Dockerfile:8–10,12–22`; `backend/test/openapi-contract.e2e-spec.ts:139–156`.
|
||||
- **Claim:** в одном обычном verify job explicit generation вызывается 3 раза, Nest build 2 раза. Одна revision проверяется без changes между этими шагами. Local standalone `verify` при отсутствующем ignored client доходит до typechecks до собственной generation.
|
||||
- **Current mechanism:** (1) CI explicit prisma generate; (2) verify заканчивается build = generate + nest build; (3) test:e2e начинает тот же build. Root `verify` не имеет preparation перед typed lint/typecheck. Installed client manifest этой версии не имеет postinstall generation. Docker production path также наследует direct generate из dependencies stage, затем генерирует в package build ещё раз.
|
||||
- **Consumer:** typed source import generated Prisma, app/test typechecks и unit tests; runtime OpenAPI generator импортирует `dist/src/app.module.js`. Последний требует свежую сборку, а не просто возможность Jest transpile source.
|
||||
- **Impact / actual cost:** подтверждён лишний запуск двух host generations и одного host Nest build; cold Docker path ещё одного generate. Потенциальный cold-local failure выводится из import/order, но fresh checkout runtime-воспроизведение не выполнялось. **Impact unmeasured** для latency/CPU; число explicit configured calls измерено статически.
|
||||
- **Why now:** same job, same SHA и одно environment дают возможность reuse без ослабления независимой CI boundary.
|
||||
- **Minimum change:** сделать standalone verify владельцем generation **до typed consumers**, оставить один Nest build; после successful verify вызвать существующий `node test/sqlite.cjs --all` без второй сборки. Удалить workflow pre-generate только после переноса подготовки в verify. Сохранить standalone `build` и `test:e2e` self-contained. В Docker перенести direct generation в development target, если production build уже гарантирует её: development должен сохранить generated client.
|
||||
- **Guarantees at risk:** свежесть generated client для schema change; наличие current dist для OpenAPI; copied proto assets и native Linux modules; standalone command UX. Кеширование host-generated dist между разными environments не предложено.
|
||||
- **Counterarguments / trade-offs:** первый CI generate необходим сейчас — его удаление само по себе ломает clean typechecks. Второй build необходим standalone `test:e2e`, но избыточен в composed CI после verify. Build в Docker не удаляется: он доказывает packaging environment. Generation/internal build cache могут уменьшать стоимость повтора, но не устраняют лишний invocation.
|
||||
- **Verification or rollback:** один clean disposable checkout с approved install: standalone verify → existing SQLite --all; лог должен показать 1 explicit host generate и 1 Nest build при тех же 19 suites и runtime schema comparison. Schema change должен влиять на типы и generator. Development и production Docker targets проверить отдельно. Rollback — scripts/workflow/stage ordering; source/data migrations не нужны.
|
||||
- **Related finding IDs:** VERIFY-004, только общая тема подготовки; не объединять repos общим tooling.
|
||||
|
||||
### VERIFY-004 — Frontend pre-commit всегда делает package build и whole-repo graph
|
||||
|
||||
- **Decision = SIMPLIFY; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `frontend/.husky/pre-commit:1–3`; installed launcher `frontend/.husky/_/h:6,14–22`; `frontend/package.json:25–26,33,36–38`; `frontend/packages/icons/package.json:9–20`; `frontend/packages/ui/package.json:9–24`; `frontend/dependency-cruiser.cjs:24–29,78–86`.
|
||||
- **Claim:** каждый successful frontend commit, включая Markdown-only, после staged lint запускает два package builds и full base graph. Команда не зависит от staged input relevance и не повторяет именно staged proof.
|
||||
- **Current mechanism:** actual hooksPath → Husky `sh -e` → Gitleaks index → lint-staged → `npm run deps` → build workspaces → depcruise src/packages working tree. Final verify/CI уже включают тот же base graph и отдельный Storybook graph.
|
||||
- **Consumer:** local commit readiness. Граф working tree может блокировать commit из-за незакоммиченного unrelated code; это отличается от selected staged ESLint input.
|
||||
- **Impact / actual cost:** 2 workspace library builds + whole-repo dependency analysis на каждый такой commit, даже при отсутствии changed code/config inputs; после final verify — повтор в той же local revision, если код не менялся. **Impact unmeasured**: hook duration, частота commits и `--no-verify` не измерены и не утверждаются.
|
||||
- **Why now:** перенести existing whole-repo proof в existing final/CI boundary проще, чем писать новый staged graph builder.
|
||||
- **Minimum change:** убрать `npm run deps` из mandatory pre-commit; сохранить full graph в final verify/CI. Сохранить fail-closed staged Gitleaks и staged lint. Если early graph feedback обязательно по принятому workflow, минимально ограничить existing command relevant source/package/TS/resolver/dependency config changes, не считать content-only import diff полноценным incremental graph proof.
|
||||
- **Guarantees at risk:** более поздняя обратная связь о cycles/import boundaries. CI guarantee не теряется при сохранении full graph и required verify; staged lint обязан получать актуальные package declarations при relevant package changes.
|
||||
- **Counterarguments / trade-offs:** standalone deps prep нужна для fresh package exports, поэтому её нельзя убрать глобально из command. Local graph catches errors до отправки и полезен, если runtime мал. Без measured duration рекомендована минимальная смена layer, не сложная cache/fingerprint orchestration.
|
||||
- **Verification or rollback:** будущий Markdown-only commit должен выполнять staged secret scan и commitlint, без package build/graph. Secret fixture должен по-прежнему fail до downstream commands; existing `scripts/test-pre-commit.mjs` использовать при изменении hook. Import cycle должен fail в full verify/CI. Проверить relevant staged TS lint на clean/fresh declarations. Rollback — вернуть одну hook command.
|
||||
- **Related finding IDs:** VERIFY-003; cross-audit 10 — implementation quality custom hook test, здесь не исследовалась.
|
||||
|
||||
## 8. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Механизм | Основание и сохраняемая гарантия |
|
||||
| --- | --- | --- |
|
||||
| DELETE, условно после FIX | Node setup/npm ci в specs verify job | После перехода на meaningful Git range Node не consumer; commits/security dependency checks остаются. VERIFY-001 |
|
||||
| SIMPLIFY | Backend host generation/build DAG и Docker duplicate generate | Один свежий artifact в каждом environment; не удалять pre-type preparation. VERIFY-003 |
|
||||
| SIMPLIFY | Mandatory full deps в frontend pre-commit | Whole-repo graph оставить final + required CI; staged Gitleaks/lint сохранить. VERIFY-004 |
|
||||
| FIX | Specs CI input range; title edited triggers | Proof должен зависеть от actual checked input. VERIFY-001/002 |
|
||||
| KEEP | Независимый CI после local checks | Local success не trusted proof для merge; install/checks повторяются на новой boundary обоснованно |
|
||||
| KEEP | SQLite functional suites и temp DB guard | Unit mocks не заменяют authorization/CAS/transaction/replay proof; e2e-env отклоняет неisolated DB |
|
||||
| KEEP | Current-build OpenAPI/runtime comparison и frontend byte check | Разные producer/consumer inputs; standalone offline generator guard сохраняется |
|
||||
| KEEP | Base и Storybook dependency graphs | TS graph и runtime reachable graph имеют разные policy/inputs |
|
||||
| KEEP | JSDOM и narrow/wide Chromium | DOM logic не доказывает реальную геометрию; viewports не OS matrix |
|
||||
| KEEP | Full coverage в final/CI и Sonar lcov | Targeted tests без coverage — iteration feedback; не заменяют global coverage floor или report consumer |
|
||||
| KEEP | Security before expensive verify; fail-closed exits | Cheap failures останавливают дорогую работу в пределах DAG; не превращать advisory/secret fail в green |
|
||||
| KEEP | Feature PR-only verification, event-scoped cancellation, immutable image labels/tags | Уже уменьшает redundant runs и защищает artifact identity; фактическая server configuration остаётся unverified |
|
||||
| KEEP | Performance/durability отдельными commands | Wall-clock shared-runner budget отдельно; schema-risk proof не отменять из-за того, что он вне ordinary verify |
|
||||
| INVESTIGATE | npm/browser cache | В workflows не задан cache input/action; persistent runner cache может уже работать. До timing/hit proof не вводить новый cache layer |
|
||||
| INVESTIGATE | Full app gate для Markdown-only application PR | Trigger сейчас whole-repo. Сначала определить affected paths и required-check semantics; не вводить path filters, оставляющие required status pending/skipped |
|
||||
| INVESTIGATE | Второй frontend tsc, перекрытие backend app/test typecheck | Invocation overlap есть; cache и program-root differences не позволяют доказать ценность удаления без diagnostics comparison |
|
||||
| INVESTIGATE | PR head freshness against updated base | Gitea не строит merge preview; actual branch protection не доступна в static checkout |
|
||||
|
||||
### Complexity-pass по фактическому `ponytail-audit`
|
||||
|
||||
- `shrink:` сократить повтор host generation/build в backend composed CI до одной подготовки; reuse existing SQLite runner. [`backend/package.json:12,28,34`; `.gitea/workflows/verify.yml:83–89`]
|
||||
- `delete:` убрать mandatory whole-repo deps/build из frontend pre-commit; replacement — existing final/CI graph gate. [`frontend/.husky/pre-commit:3`]
|
||||
- `native:` заменить пустой CI worktree diff + Node installation в specs на Git commit-range check. [`specs/.gitea/workflows/verify.yml:18–22`]
|
||||
|
||||
**net:** lines/dependencies reduction **not measured**, подтверждённых кандидатов на удаление dependency нет. Skill заканчивается оценкой net; числовой `-N lines` здесь не выдумывается, поскольку patch не готовился. Это не proposal удалить checks как таковые.
|
||||
|
||||
## 9. Cost & benefit
|
||||
|
||||
| Изменение | Ожидаемый класс benefit | Фактическое основание | Обязательный regression proof |
|
||||
| --- | --- | --- | --- |
|
||||
| Meaningful Git range без Node в specs job | CI minutes, maintenance, trust in gate | Один setup-node и один npm ci станут unnecessary для Git-only consumer; время **not measured** | Committed whitespace fail, safe range/fetch/fallback, preserved required context |
|
||||
| Metadata-only title check | Reliability; avoidance of expensive metadata rerun | Сейчас `edited` не обрабатывается; новых минут/сокращения **not measured** | Invalid title fail + valid pass без app rerun; no skipped-status bypass |
|
||||
| Backend preparation once | CI CPU/minutes и local reproducibility | 3 explicit host generate → intended 1; 2 host build → intended 1 на composed path; время **not measured** | Cold checkout/types/schema/dist, same e2e selection, runtime OpenAPI equality |
|
||||
| Lean frontend pre-commit | Developer waiting, cognitive load | Один unconditional package-build/full-graph chain можно снять с hook; время/частота **not measured** | Secrets remain fail closed, staged lint meaningful, cycle caught at required CI |
|
||||
| Iteration without full coverage/build | Local runtime и feedback latency | Existing targeted commands уже есть; usage frequency/agent tokens **not measured** | Whole-repo final/CI и relevant risk tests остаются |
|
||||
| Optional download caching | Potential network/CI minutes | Workflow cache config отсутствует; actual hit/miss state **UNVERIFIED** | Same npm ci + lock integrity; Linux native rebuild; Gitea cache compatibility |
|
||||
|
||||
Ни runtime savings приложения, ни процентов ускорения, ни сокращения agent tokens не измерено. Уменьшение числа explicit commands не равно пропорциональному уменьшению времени.
|
||||
|
||||
## 10. Recommended Tiered Verification Policy
|
||||
|
||||
Это **рекомендация**, не изменённые инструкции. Сейчас оба engineering-файла требуют `npm ci` и full verify при code/dependency completion; audit read-only не отменяет это правило задним числом.
|
||||
|
||||
| Layer | Frontend | Backend | Specs/docs/root | Guarantees retained |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `edit` | HMR + relevant `test:run -- <path>` без coverage; targeted ESLint; package preparation при первом запуске/изменении packages | Relevant Jest pattern; configured HTTP/SQLite subset при boundary changes; current generated client до typed checks | Изменённый текст/anchors; локальный diff, staged отдельно | Быстрый observable feedback; money/auth/CAS cases по затронутому риску |
|
||||
| `feature complete` | Один full verify после последнего relevant input change; targeted visual/a11y proof | Один full verify с self-contained preparation + relevant HTTP/DB suites; contract proof при wire change; durability при schema/data change | Docs full Markdown/link gate; specs meaningful diff + native validation при requirements/schema change; root config/text checks | Full types/lint/coverage/architecture/bundle и risk-specific proof |
|
||||
| `pre-commit` | Fail-closed staged secrets → staged lint/fix → commit-msg; full graph оставить final/CI | Existing policy без новых integration hooks | Specs commit-msg; root/docs не добавлять full app hook | Проверяется actual commit input; fix не выдаётся за read-only proof |
|
||||
| `CI` | Fresh lock installation → full verify + Chromium; full graph и coverage floors; security и commits required | Fresh generated client → full verify → existing isolated SQLite --all без повторного build; commits/security required | Git-range specs check; full docs links; title on edited; root commitlint | Independent merge proof; whole-repo cross-consumer regressions; no ignored errors |
|
||||
| `scheduled` | Existing daily security; deep suite только при обоснованном риске | Existing daily security; performance на dedicated/manual environment; runner-regression не каждый PR | Existing daily security; native/store checks по owned risk, не новый обязательный blanket process | Advisory drift ловится без edit; load noise не блокирует functional gate |
|
||||
|
||||
Installation локально следует привязать к manifest/lock/Node/install-state change, а не каждой source правке; в CI сохранить воспроизводимую installation. Это часть общей local/agent policy — см. cross-audit 14, без повторного отдельного finding.
|
||||
|
||||
### Что можно проверять адресно, что нельзя считать полным proof
|
||||
|
||||
| Check | Safe targeted use | Что incremental selection пропускает |
|
||||
| --- | --- | --- |
|
||||
| ESLint | Selected files с тем же typed config и fresh declarations | Новые config/rule effects на untouched files; import/type changes у consumers |
|
||||
| Jest/Vitest | Relevant suite/pattern/project без coverage during iteration | Shared helper/provider/transport регрессии в других suites, global coverage floors |
|
||||
| SQLite | Existing runner с explicit affected suite | Другие access/transaction consumers, shared migration/schema regressions; schema change требует broad DB proof |
|
||||
| Typecheck | Existing project boundary, не случайный список changed `.ts` | Reverse dependencies и public type consumers; production isolation при смешивании test roots |
|
||||
| dependency-cruiser | Можно выбирать момент full scan по relevant input paths | Whole-graph cycles/reachability/resolver effects; `changed files only` не эквивалент full graph |
|
||||
| Knip | Full at completion/CI, optional during iteration | Reverse references, config-discovered entries и dependency usage across workspaces |
|
||||
| OpenAPI | Producer current-build proof; consumer provided snapshot proof | Breaking changes against old release и delivery freshness вне explicit input set |
|
||||
| Markdown relative links | Changed source + incoming references при локальном переносе | Удаление target ломает unchanged incoming links; final docs full check сохраняется |
|
||||
| App/Storybook/image build | Local selective build для affected artifact | Shared package/theme/plugins влияют на оба frontend consumers; image native/assets не доказаны host build |
|
||||
|
||||
### Fail-fast, parallelism и resources
|
||||
|
||||
- Frontend/backend jobs действительно выстроены по `needs`; failed commits/security не запускают expensive verify. Daily event не запускает verify. Внутри scripts `&&` останавливает downstream checks.
|
||||
- Frontend pre-commit использует `sh -e`; explicit Gitleaks `|| exit $?` **передаёт failure**, а не маскирует его. Temporary OpenAPI checks и SQLite runner устанавливают ненулевой exit code при failure. `continue-on-error`/blanket `|| true` в прочитанных workflows отсутствуют.
|
||||
- Docs/specs jobs независимы и могут занимать разные runners одновременно. Последовательность одного application DAG не ограничивает server-wide capacity; README указывает настройку `runner.capacity: 1` вне repo. Actual capacity — unverified.
|
||||
- SQLite runner уже даёт каждой suite отдельный temp DB и запускает её `--runInBand`; suites с explicit listen используют port 0. Это уменьшает DB/port collisions, но не доказывает безопасность полной параллелизации всех suites. Общий generated client/dist и CPU/memory остаются shared resources.
|
||||
- **KEEP serial default:** без timings не добавлять Jest/SQLite/Chromium параллельные jobs. Не выполнять одновременно повторные builds, которые перезаписывают одни outputs. Unit/static parallelism допустим только после single preparation и доказанного resource isolation, если measured critical path это оправдывает.
|
||||
- Chromium install сейчас идёт до full verify, то есть до cheap type/lint failures. Возможен более ранний static stage, но новый составной gate/повтор package build требует cost proof; это не подтверждённый отдельный finding.
|
||||
|
||||
### Explicit guarantees retained
|
||||
|
||||
1. Денежная точность и boundary regressions остаются в existing meaningful unit/HTTP/DB checks; audit не предлагает заменить их coverage percentage.
|
||||
2. Real SQLite authorization, transactions/rollback, CAS, replay/idempotency и connection-isolation proof остаются CI functional suites; DB никогда не заменяется пользовательской.
|
||||
3. Generated client и current dist свежи до своих consumers; runtime contract сравнивается с current generator без внешней сети.
|
||||
4. Frontend accepted snapshot/type equality, full types и reverse-consumer proof остаются локальными и independent от sibling checkout.
|
||||
5. Full graph cycles/public API/reachability, Chromium layout и app/catalog packaging остаются final/CI checks.
|
||||
6. Staged secrets/history security не bypass; failures не становятся green; required contexts не ослабляются через skipped jobs.
|
||||
7. Release SHA/version identity, immutable tags, Linux native/assets, post-publication digest smoke и explicit rollout selection остаются.
|
||||
8. Durability при data/schema change и controlled lifecycle regression tests не исключаются из risk-based verification из-за их стоимости; содержательность assertions — отдельный аудит 08.
|
||||
|
||||
## 11. Top actions
|
||||
|
||||
### Quick wins: подтверждённые механизмы, малый diff
|
||||
|
||||
| Rank | Действие | Value / risk / effort | Finding |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Сделать specs CI whitespace-check явного commit range; затем убрать Node install из этого Git-only job | Убирает no-op proof и лишнюю installation; риск неверного range покрывается tiny fixture; S | VERIFY-001 |
|
||||
| 2 | Добавить PR title edit coverage, сохранив selective metadata-only execution и проверив contexts | Исправляет stale check без full pipeline rerun; S | VERIFY-002 |
|
||||
| 3 | Свести backend host preparation к одному generate/build на revision, сохранив clean standalone verify и existing e2e runner | Подтверждённый same-job повтор; cold-checkout/schema/dist proof обязателен; S | VERIFY-003 |
|
||||
| 4 | Снять unconditional full graph/build с frontend pre-commit; оставить staged security/lint и required final/CI graph | Улучшает каждый commit path без нового tooling; риск только более поздней обратной связи; S | VERIFY-004 |
|
||||
|
||||
### Решения, требующие тестового подтверждения
|
||||
|
||||
| Rank | Действие | Необходимое подтверждение | Finding |
|
||||
| --- | --- | --- | --- |
|
||||
| 5 | Устранить второй generate в backend production Docker path, оставив его development target | Обе Docker targets сохраняют current client, native modules/assets и runtime smoke | VERIFY-003 |
|
||||
|
||||
Новое ADR для перестановки уже существующих checks не требуется. Новая policy удаления обязательного gate или изменения public delivery/merge trust boundary потребует отдельного согласованного решения; такой migration здесь не предлагается. Cache tuning и удаление перекрывающихся typechecks не входят в confirmed top actions.
|
||||
|
||||
## 12. Cross-audit signals
|
||||
|
||||
- **Audit 10 — custom tooling:** `frontend/scripts/check-image-publication.cjs`, `scripts/test-pre-commit.mjs`, backend runner-regression/durability entry points; отдельно проверить maintenance/consumer ownership, без ревью их алгоритмов здесь.
|
||||
- **Audit 14 — agent/local workflow:** `frontend/.ai/conventions/engineering.md:60–62`, `backend/.ai/conventions/engineering.md:60–62`; mandatory npm ci + full completion gate и session повторяемость относятся к policy использования commands; реальные session counts здесь не измерены.
|
||||
- **Audit 08 — test intent:** `backend/test/openapi-contract.e2e-spec.ts`, SQLite functional suites и frontend browser tests; этот аудит подтверждает selection/trigger, не достаточность assertions.
|
||||
- **Audit 12 — dependency/security policy:** `backend/.gitea/workflows/verify.yml:62–65`; разные audit levels и advisory option требуют dependency-policy проверки, не переноса network audit на data edit.
|
||||
- **Audit 15 — specs/docs:** `specs/README.md:18–35`, `specs/.gitea/workflows/verify.yml`; необходимость и version ownership native OpenSpec validation — отдельно от подтверждённого no-op Git range.
|
||||
|
||||
## 13. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Статус | Самый дешёвый способ подтверждения |
|
||||
| --- | --- | --- |
|
||||
| Required commits/security/verify contexts действительно обязательны? Проверяется ли outdated base/head? | UNVERIFIED: локальный workflow не задаёт server protection | Read-only Settings/branch protection export и один existing PR status; не запускать full suite |
|
||||
| Фактическая Gitea версия поддерживает concurrency/edited/skipped status behavior как ожидает README? | UNVERIFIED для сервера; docs подтверждают edited/head semantics | Server version + существующий cancellation log; для VERIFY-002 metadata-only rename на disposable PR |
|
||||
| Насколько дорогие npm ci, package build, Chromium install, повтор Nest build? Есть ли persistent cache? | not measured | Step timestamps одного успешного PR и image run, runner cache/hit logs; после changes сравнить те же steps; не запускать full pipeline ради аудита |
|
||||
| Clean standalone backend verify действительно fails до generation? | Выводится из import/order; runtime fresh-install proof не выполнялся | При реализации VERIFY-003 один disposable clean checkout с normal approved install; проверить до/после. Не использовать локальный generated directory как baseline |
|
||||
| Можно ли удалить второй frontend tsc или backend app-only typecheck без потери diagnostics? | INVESTIGATE, не top action | Compare configured compiler roots/diagnostics на real type regression; удалить только после equivalence proof, не ради invocation count |
|
||||
| Нужен ли mandatory local graph до каждого commit, если final/CI его сохраняют? | Trade-off VERIFY-004 | Выбрать feedback layer у владельца; измерить один existing hook timing. Не писать новый graph-diff/cache инструмент |
|
||||
| Есть ли гарантированное подтверждение принятого frontend artifact перед component rollout? | UNVERIFIED для deploy repo | Read-only release/deployment record с producer SHA/consumer snapshot provenance; separate deployment owner, без обращения к банковским API |
|
||||
|
||||
Вопросы о benchmark SLAs, существующих business assertions и advisory correctness намеренно не разворачиваются в этом аудите. Отсутствие run/log evidence не означает отсутствие защиты.
|
||||
|
||||
## 14. Машинно-считываемая таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| VERIFY-001 | FIX | P2 | High | specs | specs/package.json; specs/.gitea/workflows/verify.yml | Clean CI checks empty worktree diff, not committed changes; Node install gives no content proof | S | VERIFY-002 |
|
||||
| VERIFY-002 | FIX | P2 | High | frontend,backend,specs,docs | frontend/.gitea/workflows/verify.yml; backend/.gitea/workflows/verify.yml; specs/.gitea/workflows/commits.yml; docs/.gitea/workflows/commits.yml | PR title edits leave previous Conventional Commit status stale | S | VERIFY-001 |
|
||||
| VERIFY-003 | SIMPLIFY | P2 | High | backend | backend/package.json; backend/.gitea/workflows/verify.yml; backend/.gitea/workflows/image.yml; backend/Dockerfile | Same CI revision explicitly generates client 3 times and builds twice; standalone preparation occurs late | S | VERIFY-004 |
|
||||
| VERIFY-004 | SIMPLIFY | P2 | High | frontend | frontend/.husky/pre-commit; frontend/package.json | Every commit runs package builds and full graph even without relevant staged code | S | VERIFY-003 |
|
||||
@@ -0,0 +1,328 @@
|
||||
# Аудит 12 — Dependencies & Configuration
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID/name:** 12 / Dependencies & Configuration.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Режим:** read-only для `frontend`, `backend`, `specs`, `docs`; единственный создаваемый workspace artifact — этот отчёт.
|
||||
- **Scope:** необходимость dependencies, direct/transitive/peer ownership, toolchain, compiler/test/build configuration, env authority, generated inputs и воспроизводимость локальной подготовки. CI timings, качество assertions, уязвимости и бизнес-контракты не аудировались.
|
||||
- **Skill:** установленный `ponytail-audit` прочитан через Skill tool и применён к complexity-pass. Correctness findings получены отдельно по протоколу аудита 12. Прочитан `verification-before-completion` для проверки итогового артефакта. Graphify skill прочитан; существующего `graphify-out/graph.json` в workspace нет, install/extraction не выполнялись ввиду read-only контракта.
|
||||
- **Предыдущие материалы:** `technical-audit-2026-10-11.md` прочитан как реестр гипотез; его выводы и измерения не приняты за доказательство. Отчёт 11 использован для разграничения ответственности, его timings/результаты не пересчитывались.
|
||||
|
||||
Workspace: `/Users/ksv741/Project/vibe/WealthLedger`. Найдены четыре самостоятельных checkout и отдельный root Git repo:
|
||||
|
||||
| Repo | Путь | HEAD SHA | Ветка | Начальный status |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | `codex/fix-stock-screener-race` | clean |
|
||||
| backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | `codex/add-stock-screener` | clean |
|
||||
| specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | `codex/add-stock-screener` | clean |
|
||||
| docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | `codex/add-stock-screener` | clean |
|
||||
| root, вспомогательная область | `./` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | `release/0.2.1` | clean |
|
||||
|
||||
Отдельный `deploy/` не исследовался. Root Compose рассматривается только как доступный development configuration consumer, не как доказательство текущего server deployment. Личные `.env`, базы, сертификаты и credentials не читались.
|
||||
|
||||
### Выполненные проверки
|
||||
|
||||
1. Read/Glob/Grep: repo instructions; manifests и верхние секции пяти lockfiles; выбранные lockfile nodes; compiler/build/test/lint/Storybook/Knip/dependency configs; env validators и consumers; generated-artifact callers; setup instructions.
|
||||
2. Во всех пяти Git repo: `git status --short --branch`, `git rev-parse HEAD`; в application repos также `git ls-files -- <config/generated/lock patterns>` для проверки tracking boundary.
|
||||
3. В frontend и backend после `source "$HOME/.nvm/nvm.sh" && nvm use --silent`: `node --version`, `npm --version` → **v24.21.0 / 11.19.0**; `npm ls --depth=0 --ignore-scripts` → exit 0.
|
||||
4. Там же `npm ls --all --json --ignore-scripts` с `set -o pipefail` и выводом `problems/error` через inline Node stdin reduction → оба exit 0, **`problems: []`, `error: null`**. Это проверка установленного dependency graph, не clean-install/build proof.
|
||||
5. Там же `npm query '*' --package-lock-only --ignore-scripts` с inline stdin reduction → inventory виртуального graph из lockfile, без install или записи нового lockfile. Метод и результаты ниже.
|
||||
6. Backend: `npm explain better-sqlite3 source-map-support ts-loader --ignore-scripts` → подтверждены consumers и цепочка optional/required peers. Команда ничего не удаляет.
|
||||
7. Context7: документация npm об `allowScripts` и Prisma о явной генерации client. Дополнительно прочитаны versioned Prisma v7 deployment/workflow docs. Unversioned workflow URL перенаправил на новую модель Prisma; эта страница не использована для рекомендаций к установленной 7.10.0.
|
||||
8. Итоговая проверка файла: чтение нового отчёта, whitespace-check относительно `/dev/null`, `git diff --check`, повторный status/HEAD пяти repos. Результат заключительной проверки записан в разделе 10.
|
||||
|
||||
Npm выводит предупреждение **`Unknown user config "//registry"`**. Это user-level configuration за пределами checkout; её содержимое не читалось. Команды inventory завершились успешно. Предупреждение не объявлено дефектом project configuration.
|
||||
|
||||
**Не запускались:** install/prune/update, npm audit, codegen, migrations, application/CLI watch processes, tests, lint, build, full verify, Docker, OpenSpec validation, обращения к брокерским/банковским API, CI runs, commit/PR. Исполняемые audit tools не создавались. Изменений manifests, lockfiles, sources или generated artifacts нет. Runtime-воспроизведение fresh setup и экономия времени/размера — **UNVERIFIED / not measured**.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт / FIX, P2:** самостоятельный backend quick start пропускает Prisma generation. `npm ci → migrate deploy → start:dev` не создаёт ignored `generated/prisma`, который импортирует приложение. CI, Docker и root setup явно готовят client, поэтому существующий тёплый checkout скрывает дефект подготовки. **DEPS-001**.
|
||||
2. **Факт / DELETE, P2:** backend использует штатный Nest `tsc` builder, но прямо устанавливает `ts-loader`. Его required peer приводит в graph Webpack и minimizer/Terser; optional peers Nest CLI сами по себе не требуют этой ветки. Прямая декларация `source-map-support` также не имеет project caller. **DEPS-002**.
|
||||
3. **Факт / SIMPLIFY, P2:** runtime Prisma adapter использует `better-sqlite3@12.11.1`, а smoke/durability SQL checks — прямой `13.0.3`. В production dependency tree сохраняются обе версии. Прямой драйвер нужен, но выбранный major не согласован с runtime owner. **DEPS-003**.
|
||||
4. **Факт / FIX, P3:** `start:debug` не задаёт `NODE_ENV=development`, в отличие от `start:dev`. При env example и отсутствии внешнего `NODE_ENV` debugger получает выключенный Swagger и Secure-cookie policy, хотя startup log называет окружение development. Это небольшой setup drift, не подтверждённая уязвимость. **DEPS-004**.
|
||||
5. **KEEP:** peers без прямых imports, Prisma CLI в production, независимые lockfiles, OpenAPI snapshot/types, Node alignment и разделение JSDOM/Chromium обоснованы реальными consumers. Различие TypeScript frontend/backend и Sonar/application profiles не требует автоматического выравнивания.
|
||||
6. **UNVERIFIED:** clean installation на Linux/macOS, фактическое удаление транзитивной Webpack-ветки после будущего cleanup, размер image/bundle, Sonar server/analyzer version и настройка глобального OpenSpec CLI. Их нельзя вывести из зелёного `npm ls`.
|
||||
|
||||
**Итог:** четыре подтверждённых findings: три P2 и один P3; P0/P1 не установлены. Удаление двух direct dev declarations и согласование одного runtime driver полезнее введения общего cross-repo tooling или нового валидатора.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1 Manifests, versions и lockfiles
|
||||
|
||||
| Repo | Production direct keys | Dev direct keys | Toolchain / назначение |
|
||||
| --- | ---: | ---: | --- |
|
||||
| frontend root | 23, включая 3 local workspace dependencies | 52 | React 19.3.0, Vite 8.3.0, TS 5.9.3, Vitest 5.0.0, Storybook 10.6.0 |
|
||||
| backend | 26 | 37 | Nest 12.x, Prisma 7.10.0, TS 6.0.3, Jest 30.5.1 / ts-jest 29.4.12 |
|
||||
| specs | 0 | 5 | Commit/release tooling; OpenSpec отсутствует в local npm dependency contract |
|
||||
| docs | 0 | 7 | Commit/release tooling + Markdown lint/relative-links |
|
||||
| root | 0 | 5 | Commit/release tooling |
|
||||
|
||||
Метод: подсчёт ключей `dependencies` и `devDependencies` прочитанных root manifests. Это число declarations, не уникальных packages во всём workspace. Дополнительно полностью прочитаны **три frontend workspace manifests**, то есть всего восемь manifests. Их peer/dev declarations не суммируются с root: одна и та же установка deduped, но contracts владельцев самостоятельны.
|
||||
|
||||
Все пять прочитанных lockfile headers — npm **lockfileVersion 3**; root names/versions/direct declarations в просмотренных header sections согласованы с соответствующими manifests. Другой tracked JS lockfile в выбранном application tracking inventory не обнаружен. Все пять `.nvmrc` содержат `24.21.0`; application Dockerfiles используют `node:24.21.0-bookworm-slim`. Версии registry dependencies в direct manifests exact; `file:` links frontend намеренные.
|
||||
|
||||
### 3.2 Transitive / peer / optional / platform inventory
|
||||
|
||||
Метод: один `npm query '*' --package-lock-only --ignore-scripts` на application repo. Из JSON отброшены nodes с пустым `location`; посчитаны nodes и truthy поля `dev`, `optional`, `peer`, `hasInstallScript`, наличие `os || cpu`. Workspace nodes включены. Это **npm virtual graph nodes**, не объём диска, не только external names и не число реально установленных platform packages.
|
||||
|
||||
| Метрика | frontend | backend |
|
||||
| --- | ---: | ---: |
|
||||
| Nodes с непустым location | 1294 | 1119 |
|
||||
| `dev: true` | 1192 | 738 |
|
||||
| `optional: true` | 191 | 43 |
|
||||
| `peer: true` | 0 | 43 |
|
||||
| `hasInstallScript: true` | 4 | 8 |
|
||||
| Есть `os` или `cpu` | 178 | 34 |
|
||||
|
||||
Flags пересекаются и не образуют разбиение. `peer: 0` у frontend **не означает отсутствия peer contracts**: прямо объявленный peer provider не обязан иметь этот installation flag. Например, Mantine charts → Recharts → react-is доказаны installed manifests. Platform entries в lockfile не означают, что на Darwin установлены все Linux/Windows binaries.
|
||||
|
||||
Lockfile-marked lifecycle nodes:
|
||||
|
||||
- Frontend: `esbuild@0.28.2`, `fsevents@2.3.3` (optional), `msw@2.15.0`, `unrs-resolver@1.12.2`.
|
||||
- Backend: `@parcel/watcher@2.6.0`, nested `better-sqlite3@12.11.1`, `@prisma/engines@7.10.0`, `@scarf/scarf@1.4.0`, `bcrypt@6.0.0`, `prisma@7.10.0`, `protobufjs@7.6.5`, `unrs-resolver@1.12.2`.
|
||||
|
||||
Frontend `allowScripts` разрешает esbuild/fsevents; backend разрешает актуальные native/Prisma/protobuf/resolver nodes и содержит разрешение для `better-sqlite3@13.0.3`, у которого install hook отсутствует. **Это не два native build hooks:** версия 13 поставляет prebuilds, версия 12 имеет `prebuild-install || node-gyp rebuild`. Неисполнение MSW/scarf и optional/fallback hooks не объявлено поломкой без proof необходимого поведения. Policy owner — root manifest каждого npm repo, не dependency/workspace manifest.
|
||||
|
||||
### 3.3 Dependency Decision Register
|
||||
|
||||
Полный перечень exact versions доступен в `frontend/package.json:39–116`, `backend/package.json:40–105`; ниже все direct declarations сгруппированы по реальным ролям. `KEEP` для группы означает установленную роль в stack/configuration, не semantic audit каждого её API.
|
||||
|
||||
| Repo / class | Dependencies | Consumer / назначение | Decision |
|
||||
| --- | --- | --- | --- |
|
||||
| frontend runtime UI | `react`, `react-dom`, `react-router`, `@mantine/core`, `@mantine/hooks`, `@mantine/notifications`, `motion` | App/providers/routes, form/view imports, RouteTransition | KEEP |
|
||||
| frontend runtime charts/dates | `@mantine/charts`, `recharts`, `react-is`, `@mantine/dates`, `dayjs` | DonutChart/AreaChart, DateInput/DatePickerInput, locale; Recharts/react-is — peers | KEEP |
|
||||
| frontend forms/i18n | `react-hook-form`, `@hookform/resolvers`, `zod`, `i18next`, `react-i18next` | AuthForm, BankCard/Operation forms и локализованные schemas | KEEP |
|
||||
| frontend runtime HTTP/cache | `@tanstack/react-query`, `openapi-fetch`, `openapi-react-query` | `src/shared/api/client.ts:1–4`, App QueryClient/provider | KEEP; разные уровни одного typed flow |
|
||||
| frontend local packages | `@wealthledger/icons`, `@wealthledger/tokens`, `@wealthledger/ui` | App и Storybook theme/assets/components; exports → dist/JSON | KEEP |
|
||||
| frontend build/type/PWA | `typescript`, `@types/node`, `@types/react`, `@types/react-dom`, `vite`, `@vitejs/plugin-react`, `@rolldown/plugin-babel`, `@babel/core`, `babel-plugin-react-compiler`, `vite-plugin-pwa` | TS configs, Vite React compiler preset/PWA; package library builds | KEEP |
|
||||
| frontend tests/catalog | `vitest`, `@vitest/coverage-v8`, `@vitest/browser-playwright`, `playwright`, `jsdom`, `@testing-library/react`, `@testing-library/jest-dom`, `msw`, `storybook`, `@storybook/react-vite`, `@storybook/addon-docs` | Vitest projects/setup/server, browser provider, `.storybook/main.ts` | KEEP |
|
||||
| frontend graph/unused | `dependency-cruiser`, `knip` | scripts и разные graph/unused-declaration policies | KEEP |
|
||||
| frontend lint core | `eslint`, `@eslint/js`, `typescript-eslint`, `globals`, `eslint-import-resolver-typescript`, `eslint-plugin-import-x` | `eslint.config.mjs:1–21,133–164` | KEEP |
|
||||
| frontend lint plugins | `@eslint-react/eslint-plugin`, `@stylistic/eslint-plugin`, `@tanstack/eslint-plugin-query`, `@vitest/eslint-plugin`, `eslint-plugin-jest-dom`, `eslint-plugin-no-unsanitized`, `eslint-plugin-perfectionist`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`, `eslint-plugin-regexp`, `eslint-plugin-security`, `eslint-plugin-sonarjs`, `eslint-plugin-storybook`, `eslint-plugin-testing-library`, `eslint-plugin-unicorn` | Все импортированы/подключены flat config; production/test/story profiles различаются | KEEP; полезность отдельных rules вне этого аудита |
|
||||
| frontend hooks/release | `husky`, `lint-staged`, `@commitlint/cli`, `@commitlint/config-conventional`, `release-it`, `@release-it/conventional-changelog`, `conventional-changelog-conventionalcommits` | prepare/hooks/staged scripts, commitlint, release plugin/preset | KEEP |
|
||||
| backend HTTP/framework | `@nestjs/common`, `@nestjs/core`, `@nestjs/config`, `@nestjs/platform-express`, `@nestjs/swagger`, `class-transformer`, `class-validator`, `reflect-metadata`, `rxjs` | Nest modules/bootstrap/DTO validation; metadata/RxJS — framework peers | KEEP |
|
||||
| backend auth/cookies | `@nestjs/jwt`, `@nestjs/passport`, `passport`, `passport-jwt`, `bcrypt`, `cookie-parser` | AuthModule, JWT strategy/guard, password hashing, configureApp | KEEP |
|
||||
| backend DB/config | `@prisma/adapter-better-sqlite3`, `@prisma/client`, `prisma`, `better-sqlite3`, `dotenv` | PrismaService, compiled generated client, image migrations, smoke/durability SQL, `prisma.config.ts` | KEEP direct contracts; driver alignment DEPS-003 |
|
||||
| backend integrations/logs | `@grpc/grpc-js`, `@grpc/proto-loader`, `cheerio`, `lru-cache`, `nestjs-pino`, `pino-http` | TbankClient RPC/TLS/proto, NCR HTML+bounded cache, typed loggingOptions | KEEP |
|
||||
| backend build/debug | `@nestjs/cli`, `@nestjs/schematics`, `typescript`, `ts-node`, `tsconfig-paths`, `ts-loader`, `source-map-support` | CLI collection/tsc; `test:debug` explicitly preloads ts-node/paths; Webpack route не выбран | KEEP первых пяти; DELETE двух последних — DEPS-002 |
|
||||
| backend tests/types | `@nestjs/testing`, `jest`, `ts-jest`, `supertest`; `@types/bcrypt`, `@types/cookie-parser`, `@types/express`, `@types/jest`, `@types/node`, `@types/passport-jwt`, `@types/supertest` | configured unit/HTTP tests, Jest transformer, application/test TS | KEEP |
|
||||
| backend lint | `eslint`, `@eslint/js`, `typescript-eslint`, `globals`, `@darraghor/eslint-plugin-nestjs-typed`, `@stylistic/eslint-plugin`, `eslint-import-resolver-typescript`, `eslint-plugin-import-x`, `eslint-plugin-jest`, `eslint-plugin-n`, `eslint-plugin-perfectionist`, `eslint-plugin-regexp`, `eslint-plugin-security`, `eslint-plugin-sonarjs` | `eslint.config.mjs:2–15,25–40,79–123` | KEEP |
|
||||
| backend/specs/docs/root release | `@commitlint/cli`, `@commitlint/config-conventional`, `release-it`, `@release-it/conventional-changelog`, `conventional-changelog-conventionalcommits` | Локальные scripts/configs каждого независимого repo | KEEP; duplication across repos не дефект |
|
||||
| docs validation | `markdownlint-cli`, `markdownlint-rule-relative-links` | manifest lint + `.markdownlint-rule-relative-links.cjs:1` | KEEP |
|
||||
|
||||
Peer compatibility контрпроверена установленным graph. В частности, `backend/node_modules/ts-jest/package.json:64–71` принимает Jest 29/30 и TS `>=4.3 <7`: различие major names **не является конфликтом**. Nest CLI локально требует TS `~6.0.2`; frontend остаётся на TS 5.9.3 с отдельными browser/package configs. Общий compiler lockfile здесь не нужен.
|
||||
|
||||
### 3.4 Potential Unused (not proven)
|
||||
|
||||
| Initial candidate | Почему отсутствие project import недостаточно | Решение / дешёвая следующая проверка |
|
||||
| --- | --- | --- |
|
||||
| `recharts`, `react-is` | Mantine charts peer `recharts >=3.2.1`; Recharts peer `react-is` (`node_modules/@mantine/charts/package.json:37–42`, `node_modules/recharts/package.json:89–92`) | KEEP explicit providers, не перекладывать contract на transitives |
|
||||
| Backend прямой `better-sqlite3` | Dynamic require в durability и JS string, исполняемой внутри container smoke | KEEP direct package; согласовать version, не удалять consumer |
|
||||
| `reflect-metadata`, `rxjs`, `passport` | Framework/decorator/strategy integration и peer requirements | KEEP; отсутствие import в отдельных business files не unused proof |
|
||||
| `@nestjs/schematics` | `nest-cli.json:3` выбирает collection; CLI загружает её по строке | KEEP |
|
||||
| `conventional-changelog-conventionalcommits` | Release preset задан строкой `name: conventionalcommits`; frontend Knip ignore осознанно отражает dynamic loading | KEEP |
|
||||
| `@babel/core`, `babel-plugin-react-compiler` | Root Vite config вызывает `reactCompilerPreset()`; preset integration — consumer без прямого compiler-plugin import | KEEP |
|
||||
| `unrs-resolver`, `fsevents`, platform packages | Transitive tooling/optional native resolution, platform-specific packaging | Не переносить транзитивные packages в direct; не вычищать lock nodes вручную |
|
||||
|
||||
`ts-loader` и прямая декларация `source-map-support` после проверки config/CLI consumers переведены из кандидатов в DEPS-002. Других доказанных unused direct dependencies не установлено. Массовый автоматический removal по текстовым imports не предлагается.
|
||||
|
||||
### 3.5 Configuration Authority Matrix
|
||||
|
||||
| Значение / область | Authoritative owner | Consumers / дублирование | Вывод |
|
||||
| --- | --- | --- | --- |
|
||||
| Node 24.21.0 | `.nvmrc` каждого repo; manifest engine; соответствующий Docker base | setup-node читает local `.nvmrc`; backend ESLint node version тоже 24.21.0 | KEEP согласованность; npm 11.19.0 фактически проверен на host, версия внутри image не проверена |
|
||||
| Direct versions / lifecycle permissions | Local `package.json` + `package-lock.json`; `allowScripts` root npm project | npm ci, Docker, scripts; independent Renovate configs | KEEP локальную authority; не вводить соседний checkout как toolchain source |
|
||||
| Backend required env/defaults | `src/config/env.validation.ts:21–33,102–114` | ConfigModule → ConfigService → Prisma/auth/logging/TbankClient/main; example воспроизводит defaults | Один runtime owner для PORT=3000, rate=5, endpoint, LOG_LEVEL=info; mismatch defaults не найден |
|
||||
| Backend mode | Explicit `NODE_ENV` launch context | start:dev, Docker/Compose задают development/production; main/auth читают process.env напрямую | DEPS-004: один debug entry point выбивается из того же launch contract |
|
||||
| Development env | `.env.example` каждого app и root `.env.development.example` | Local manual setup; root development Compose | Required backend: DB URL, frontend origin, JWT secret, encryption key; реальные secrets не исследованы |
|
||||
| API base/proxy | Frontend `VITE_API_BASE_URL` в `client.ts:21–26`; Vite `API_PROXY_TARGET` в `vite.config.ts:54–60` | Local example = absolute localhost; Docker = empty same-origin base + backend proxy | Это browser/build-time base и dev-server target, а не два competing endpoints |
|
||||
| Frontend TS language/alias | `tsconfig.base.json`, `tsconfig.app.json` | Vite/Vitest alias `@ → src`; packages имеют declaration configs | Alias согласован; отдельные app/config module modes имеют разных consumers |
|
||||
| Frontend graph resolution | `tsconfig.dependency-cruiser.json` | Package aliases указывают source для архитектурных edges вместо built dist | KEEP deliberate analysis projection; runtime всё ещё использует package exports |
|
||||
| Backend output/module resolution | `tsconfig.json` + build/dev profiles, `nest-cli.json` | dist/src/main.js, dist-dev; copied proto assets; Jest ts-jest и e2e `.js` mapper | ESM Nest integration и CJS generated Prisma — явные contracts; drift failures не доказаны |
|
||||
| Sonar TS projection | `sonar-project.properties:11–12`, `tsconfig.sonar.json` каждого app | CI использует отдельный profile для заявленного старого analyzer | KEEP до проверки server analyzer; это не app typecheck и не основание слить configs |
|
||||
| Tests/platforms | `vitest.config.ts`, backend manifest Jest + `test/jest-e2e.json` | JSDOM, Chromium narrow/wide; src unit vs test HTTP suites | KEEP разные boundaries; одинаковые transform fragments не требуют общего runner |
|
||||
| Browser/RPC deadlines | `frontend/src/shared/api/client.ts:15–18`; `backend/src/integrations/tbank/tbank.client.ts:49–54` | HTTP transport и upstream RPC имеют собственные retry budgets | Одинаковые 15 s не доказывают единую policy; budget composition — audit 07 |
|
||||
| Generated Prisma | `prisma/schema.prisma:1–5`; `package.json:12` build | generated/prisma → PrismaService → TS build; ignored source | DEPS-001 касается самостоятельного quick start; CI/Docker готовят client явно, standalone checks — отдельный audit 11 |
|
||||
| Public API artifacts | Backend openapi/generator; явно доставленный frontend snapshot + local generator version | `openapi-fetch` types, temporary byte comparison | KEEP committed frontend snapshot/types, backend temporary output не коммитить автоматически |
|
||||
| Specs native CLI | `specs/README.md:18–31`, предоставленная установка OpenSpec | Отдельная native validation; npm verify не запускает CLI | INVESTIGATE только если clean setup этой workflow становится требованием; CLI version не проверялась |
|
||||
|
||||
Широкого набора неиспользуемых production env flags не найдено: каждое поле `Environment` имеет validator/default и конкретного ConfigService consumer. `NODE_ENV`, `CI`, `HUSKY` и `KNIP_DISABLE_RAW_TRANSFER` — launch/tooling context, а не параметры финансовой модели. Корректность бизнес-политик, опций scheduler и закрытых external integrations не исследовалась.
|
||||
|
||||
### 3.6 Tool Overlap Matrix
|
||||
|
||||
| Инструменты / configs | Реальные зоны применения | Verdict |
|
||||
| --- | --- | --- |
|
||||
| ESLint + Stylistic; aliases `format`, `lint:fix` | Одна ESLint flat-config execution, stylistic plugin форматирует тот же JS/TS | KEEP; второго project formatter в direct manifests нет |
|
||||
| ESLint import rules + dependency-cruiser + Knip | Local import/type checks; graph cycles/layers/reachability; unused entries/exports/declarations | KEEP разные доказательства; повторные triggers — audit 11 |
|
||||
| Vite/Rolldown + Babel React compiler | Bundling/React transform против React compiler optimization preset | KEEP; Babel не заменяет Vite |
|
||||
| Nest tsc + ts-jest | Production TS compilation против Jest transform | KEEP |
|
||||
| Nest tsc + ts-loader/Webpack | Выбранный tsc builder против невыбранного Webpack builder | DELETE unused direct Webpack entry — DEPS-002 |
|
||||
| Vitest JSDOM + browser-playwright + RTL/MSW | Fast component/transport tests против actual Chromium layout; RTL/MSW дают interactions/network simulation | KEEP; browser dependency не равна дублирующему test framework |
|
||||
| Zod/RHF resolver + backend class-validator | Browser form validation и server trust-boundary DTO validation в независимых repos | KEEP; generic cross-repo schema layer не нужен |
|
||||
| OpenAPI JSON + generated TS | Явный provider artifact и derived consumer types | KEEP; source JSON и type output не взаимозаменяемы |
|
||||
| Markdown main/changelog profiles | Общий lint и специальная повторяемость release headings, через `extends` | KEEP |
|
||||
| Application + Sonar tsconfigs | Текущий compiler против старого external analyzer | KEEP version-compatibility adapter; точная server version UNVERIFIED |
|
||||
|
||||
### 3.7 Generated sources и reproducibility
|
||||
|
||||
- `git ls-files` подтверждает tracking frontend `src/shared/api/spec/openapi.json` и `src/shared/api/generated/schema.d.ts`; workspace package dist не tracked. `check-openapi.mjs:6–12,16–33,41–43` генерирует временный type artifact и сравнивает с committed, затем удаляет temp. Проверка во время аудита не запускалась.
|
||||
- Backend `.gitignore:7–8,69–70` исключает generated Prisma и `openapi.json`. `generate-openapi.cjs:4–15,23–35` задаёт disposable configuration, использует Nest preview и пишет provider artifact. Runtime correctness/свежесть artifact не выводятся из наличия файла на диске.
|
||||
- Backend `build` владеет Prisma generation; frontend package builds владеют JS/CSS/declarations. Требовать committed private outputs вместо этих build owners оснований нет.
|
||||
- `longs: String`, разные `defaults: true/false` gRPC loaders (`tbank.client.ts:194–209,232–241`) сохраняют precision/field-presence contracts. Их нельзя консолидировать только по одинаковым именам packages/options.
|
||||
|
||||
Coverage risk-based: inventory manifests/configs полный в перечисленных областях; semantic tracing сосредоточен на fresh setup, выбранном compiler, peers без imports, native runtime boundary и mode consumers. Не исследованы все APIs каждого пакета, вся browser support matrix, actual images, registry provenance, advisories и глобальные editor/user configs.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### DEPS-001 — Backend quick start не подготавливает generated Prisma Client
|
||||
|
||||
- **Decision:** FIX. **Severity:** P2. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `backend/README.md:9–18`; `backend/package.json:11–18`; `backend/prisma/schema.prisma:1–5`; `backend/.gitignore:7–8`; `backend/src/prisma/prisma.service.ts:3–5,16–21`; `backend/node_modules/@prisma/client/package.json:264–275`; контргарантии: `backend/.gitea/workflows/verify.yml:83–85`, `backend/Dockerfile:7–10`, `README.md:97–104`.
|
||||
- **Claim:** инструкция для самостоятельного свежего checkout заканчивается `start:dev` без шага, создающего импортируемый client. Это дефект prerequisites, не требование коммитить generated code.
|
||||
- **Current mechanism:** schema output — `generated/prisma`; install hooks client 7.10.0 не генерируют его; root project не содержит generation lifecycle. `migrate deploy` применяет SQL, не генерирует client. `start:dev` запускает Nest watch, не `build` и не `prisma generate`. PrismaService импортирует отсутствующий module.
|
||||
- **Impact / actual cost:** clean quick start не может успешно скомпилировать этот import; warm checkout с существующим ignored output работает и скрывает prerequisite. **Impact unmeasured**: fresh-checkout reproduction и потраченное setup time не измерены.
|
||||
- **Why now:** backend объявляет самостоятельный local setup; текущие Prisma generator/package scripts позволяют доказать отсутствующий preparation owner до будущего dependency update.
|
||||
- **Minimum change:** добавить перед `migrate deploy` в backend quick start существующий `npm run build` — он уже выполняет `prisma generate && nest build`, как root setup. Не добавлять generator wrapper, postinstall side effect или tracked private client. Если требуется self-contained запуск каждого watch script, отдельно выбрать generation перед `start:dev/debug`, не дублируя оба решения автоматически.
|
||||
- **Guarantees at risk:** generation должен остаться до TS consumers; схема, migration SQL и существующая база не должны изменяться при preparation. Build не заменяет `migrate deploy`.
|
||||
- **Counterarguments / trade-offs:** CI и Docker уже готовят client; root README содержит правильную последовательность. Это ограничивает failure до самостоятельного fresh setup, но не исправляет эту публичную инструкцию. Полный build чуть дороже standalone generate, зато использует existing owner без нового tooling.
|
||||
- **Verification or rollback:** при будущей правке выполнить документированный setup в отдельном disposable checkout с новой SQLite fixture, убедиться в наличии client и успешном старте; тестовые секреты/данные не из live `.env`. Для text-only правки сначала достаточно сверить command chain; runtime proof нужен перед заявлением fresh setup verified. Rollback — revert правки инструкции; generated outputs не коммитить.
|
||||
- **Related finding IDs:** DEPS-004. Related audit signal: audit 11 — owner подготовки standalone checks.
|
||||
|
||||
Prisma v7 reference явно отмечает отсутствие generation в `migrate deploy`: <https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/development-and-production#production-and-testing-environments>.
|
||||
|
||||
### DEPS-002 — Неиспользуемый ts-loader удерживает альтернативную Webpack toolchain
|
||||
|
||||
- **Decision:** DELETE. **Severity:** P2. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `backend/package.json:12,15–18,98–104`; `backend/nest-cli.json:1–10`; `backend/package-lock.json:14287–14310,13998–14009`; installed versioned mechanism: `backend/node_modules/@nestjs/cli/lib/configuration/defaults.js:10–18`, `backend/node_modules/@nestjs/cli/lib/compiler/helpers/get-builder.js:9–15`, `backend/node_modules/@nestjs/cli/actions/build.action.js:87–110,153–176`, `backend/node_modules/@nestjs/cli/package.json:84–118`.
|
||||
- **Claim:** прямые `ts-loader@9.6.2` и `source-map-support@0.5.21` не обслуживают configured project build/debug/test path. Главный лишний entry — ts-loader, который имеет required Webpack peer.
|
||||
- **Current mechanism:** Nest defaults = tsc, webpack=false; local config не выбирает builder/webpack, scripts не передают webpack option. CLI импортирует Webpack compiler динамически только для выбранного Webpack branch. `npm explain` подтверждает цепь root ts-loader → required Webpack → minimizer-webpack-plugin → Terser → source-map-support. Nest CLI отмечает эти bundler peers optional. Прямых project imports/preloads source-map-support нет; `test:debug` использует ts-node с собственным `@cspotcode/source-map-support`.
|
||||
- **Impact / actual cost:** поддерживается unused compiler route и его peer/transitive graph; обновления/compatibility review затрагивают packages, не участвующие в configured compilation. **Impact unmeasured**: размер, install time, audit surface и число исчезающих transitive nodes после re-resolution не измерены. Terser — известный transitive consumer, а не повод удалить его dependency файлы вручную.
|
||||
- **Why now:** установленный Nest 12 сделал Webpack optional/lazy; прежняя прямая dependency больше не требуется обычному tsc builder. Исходное намерение держать резервный compiler не найдено.
|
||||
- **Minimum change:** убрать только две direct dev declarations, штатно обновить lockfile в отдельной implementation task. Сохранить Nest CLI, TypeScript, ts-node, tsconfig-paths и любую dependency, которую новый resolved graph всё ещё требует. Не переписывать Nest build на bare tsc: CLI владеет proto assets/watch lifecycle.
|
||||
- **Guarantees at risk:** production dist layout, copied proto contracts, watch/debug и source-mapped Jest debugging. Debugger должен продолжать использовать существующий ts-node path.
|
||||
- **Counterarguments / trade-offs:** ручной `nest build --webpack` потребует отдельной установки; такого поддерживаемого режима в scripts/config/docs/CI не найдено. Optional peer consumer не равен active project consumer. Транзитивный source-map-support может остаться, если его потребует другой retained package.
|
||||
- **Verification or rollback:** после будущего удаления — clean `npm ci`, `npm ls --all`, `npm explain webpack source-map-support`, canonical backend `verify` + `test:e2e`, watch/debug на disposable configuration; проверить proto asset path. При сохранении Webpack найти новый retained consumer вместо обещания полного удаления. Rollback — вернуть две declarations и предыдущий lockfile вместе.
|
||||
- **Related finding IDs:** DEPS-001, DEPS-004.
|
||||
|
||||
### DEPS-003 — Runtime и SQL-проверки используют разные major SQLite-драйвера
|
||||
|
||||
- **Decision:** SIMPLIFY. **Severity:** P2. **Confidence:** High. **Effort:** M.
|
||||
- **Evidence:** `backend/package.json:50–53,144–148`; `backend/package-lock.json:3762–3784,6284–6294`; `backend/src/prisma/prisma.service.ts:16–21`; `backend/test/bank-cards-durability.cjs:24,74–81`; `backend/test/container-smoke.cjs:34–38,58–64`; `backend/Dockerfile:21–22,31–35`; `backend/node_modules/better-sqlite3/package.json:24–36,48–54`; `backend/node_modules/@prisma/adapter-better-sqlite3/node_modules/better-sqlite3/package.json:18–23,40–46`.
|
||||
- **Claim:** прямой `better-sqlite3` нужен реальным checks, но версия 13.0.3 заставляет удерживать второй driver поверх adapter-owned 12.11.1. Это не unused package и не npm peer error.
|
||||
- **Current mechanism:** adapter 7.10.0 требует `better-sqlite3 ^12.6.0` и получает nested 12.11.1; direct exact13 не удовлетворяет этому range. Prisma CLI принимает direct13 как optional peer `>=9`. Smoke/durability SQL читают root13, а PrismaService работает через nested12. Обе dependency paths production и сохраняются после omit-dev prune.
|
||||
- **Impact / actual cost:** две packaging/native compatibility boundaries; SQL-level проверка использует другой driver generation, чем application adapter. При этом smoke также реально запускает приложение, поэтому нельзя утверждать отсутствие runtime/native proof вообще. **Impact unmeasured**: disk/image bytes, затраты install и поведенческая разница bundled SQLite не измерены.
|
||||
- **Why now:** несовместимые major уже присутствуют в local lock и production manifest; update direct driver независимо от adapter продолжает закреплять лишнюю boundary.
|
||||
- **Minimum change:** сохранить direct dependency, но выбрать exact версию, удовлетворяющую adapter range — текущая runtime 12.11.1 является минимальным кандидатом для консолидации. После штатного lock update подтвердить deduplication. Если нужен именно v13, ждать/выбрать официально поддерживающий её adapter; не принуждать несовместимый adapter override. Разрешение install hook v12 сохраняется; stale разрешение v13 можно удалить только после согласования новой policy.
|
||||
- **Guarantees at risk:** SQLite persistence, integrity checks, transaction/rollback, native ABI compatibility, read-only image migrations и финансовая durability. Не менять storage format и migration history ради dependency cleanup.
|
||||
- **Counterarguments / trade-offs:** v13 поставляет готовые N-API prebuilds, v12 использует install hook и возможную сборку; это реальное различие, а не две одинаковые lifecycle команды. Но production adapter уже зависит от v12, поэтому direct13 не устраняет v12 install cost. Требование тестировать намеренно разные SQLite generations не найдено; future advisories проверяются отдельно перед version change.
|
||||
- **Verification or rollback:** будущая clean installation на целевых OS/architecture; подтвердить один resolved driver и adapter-compatible version; backend `verify`, SQLite/e2e, built-image smoke и существующий durability runner с disposable baseline. Не запускать durability на рабочей базе. Rollback — восстановить manifest/lock/policy совместно и пересобрать image; database rollback не требуется при чисто dependency/config change без миграций.
|
||||
- **Related finding IDs:** DEPS-002.
|
||||
|
||||
### DEPS-004 — Debug entry point теряет development mode
|
||||
|
||||
- **Decision:** FIX. **Severity:** P3. **Confidence:** High. **Effort:** S.
|
||||
- **Evidence:** `backend/package.json:16–17`; `backend/.env.example:1–10`; `backend/src/config/swagger.config.ts:1–3`; `backend/src/main.ts:49–57`; `backend/src/modules/auth/presentation/auth.controller.ts:131–136`; `backend/src/config/env.validation.ts:9–19`.
|
||||
- **Claim:** при отсутствии унаследованного `NODE_ENV` одинаковые dev/debug watch profiles включают разные mode-sensitive policies. Это подтверждённое различие конфигурации, не доказанный отказ auth в конкретном browser.
|
||||
- **Current mechanism:** start:dev задаёт NODE_ENV=development, start:debug только `--debug --watch --path tsconfig.dev.json`. Env example не содержит NODE_ENV; Environment validator не нормализует это поле. Swagger открывается только при точном development, cookie Secure выключается только при точном development, log fallback пишет development даже при undefined.
|
||||
- **Impact / actual cost:** debugger не воспроизводит development HTTP configuration; Swagger отсутствует, cookies получают иной flag. Поведение Secure cookies на localhost зависит от client, его отказ не заявляется. **Impact unmeasured**: runtime debugger/browser scenario не запускался.
|
||||
- **Why now:** исправляется одной строкой existing entry point без env framework и изменения production defaults.
|
||||
- **Minimum change:** добавить `NODE_ENV=development` в start:debug тем же способом, что уже используется в start:dev. Не задавать global permissive default и не ослаблять production cookie/Swagger policy.
|
||||
- **Guarantees at risk:** production Secure cookies и отключённый Swagger должны сохраниться; debug остаётся отдельным осознанным local launch.
|
||||
- **Counterarguments / trade-offs:** внешняя shell/.env может уже задавать NODE_ENV — тогда расхождения нет. Fresh documented env такого prerequisite не устанавливает. Настройка только launch script намеренно меньше, чем новая unified environment abstraction.
|
||||
- **Verification or rollback:** после будущей правки сравнить dev/debug launch configuration на disposable env, убедиться в development Swagger; существующая production-mode проверка должна сохранить закрытый Swagger/Secure cookie. Rollback — revert одной script value.
|
||||
- **Related finding IDs:** DEPS-001, DEPS-002.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Объект | Основание / сохраняемая гарантия |
|
||||
| --- | --- | --- |
|
||||
| DELETE | Direct ts-loader и source-map-support — DEPS-002 | tsc builder остаётся; transitive packages удаляются только штатным dependency resolution |
|
||||
| SIMPLIFY | Две версии better-sqlite3 — DEPS-003 | Сохранить прямой SQL consumer и adapter-supported native driver; consolidation после SQLite/image/durability proof |
|
||||
| FIX | Fresh quick start — DEPS-001 | Explicit build/generate до import, без committed private outputs и install hooks |
|
||||
| FIX | Debug NODE_ENV — DEPS-004 | Local development mode, без изменения fail-closed production policy |
|
||||
| KEEP | Prisma CLI production dependency | Image и Compose migration запускают `node_modules/prisma/build/index.js`; перенос в dev сломает omit-dev image |
|
||||
| KEEP | Recharts/react-is, reflect-metadata/RxJS/passport peers | Dynamic/framework consumers; отсутствие business import не доказательство ненужности |
|
||||
| KEEP | Bcrypt, native SQLite, proto loaders и TLS CA option | Auth, persistence, precision/field presence, реальная сеть/сертификаты; stdlib replacement не доказан эквивалентным |
|
||||
| KEEP | OpenAPI snapshot и generated TS | Независимый consumer repo с offline local contract/type check |
|
||||
| KEEP | Отдельные compiler/test/Sonar profiles | Разные runtime/module/analyzer environments и fixtures, не искусственно дублирующий один compiler |
|
||||
| KEEP | Независимые lockfiles/release/commitlint configs | Каждый repo устанавливается и выпускается без соседних checkout |
|
||||
| KEEP | Restrictive allowScripts и exact versions | Не давать wildcard разрешения ради исчезновения lifecycle warnings |
|
||||
| INVESTIGATE | Specs OpenSpec CLI ownership | README допускает внешнюю незакреплённую CLI; local npm ci не обещает её установку. Pin только при требовании reproducible native workflow |
|
||||
| INVESTIGATE | Native/image footprint и Sonar projection | Фактические artifacts/server version не проверены; размер и ложные diagnostics нельзя выводить из количества packages |
|
||||
|
||||
### Complexity-pass в формате ponytail-audit
|
||||
|
||||
- **`delete:`** убрать direct ts-loader, активирующий невыбранную Webpack-ветку; replacement: существующий Nest tsc builder. [`backend/package.json:101`, `backend/nest-cli.json`]
|
||||
- **`shrink:`** согласовать direct better-sqlite3 с adapter-supported runtime version; replacement: один совместимый driver для приложения и SQL checks. [`backend/package.json:50–53`, `backend/package-lock.json:3762–3784`]
|
||||
- **`delete:`** убрать direct source-map-support declaration; replacement: ничего в project code, необходимые transitive consumers сохраняет npm. [`backend/package.json:98`]
|
||||
|
||||
**net: -2 manifest lines, -2 direct deps possible.** Число относится только к двум предложенным direct declaration removals. Lockfile/transitive line savings не измерены. Consolidation driver может убрать одну version instance, но не сам required direct dependency; clean resolution и compatibility proof ещё нужны.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| Действие | Класс выгоды | Фактическое измерение | Обязательные границы |
|
||||
| --- | --- | --- | --- |
|
||||
| DEPS-001: explicit preparation | Setup reliability, cognitive load/agent navigation | Missing prerequisite доказан статически; setup minutes **not measured** | Fresh client до TS import; безопасная disposable DB; не трогать существующие secrets |
|
||||
| DEPS-002: remove unused compiler entries | Dependency maintenance, потенциальные install/CI cost | 2 direct entries; Webpack peer chain доказана; minutes/bytes/closure reduction **not measured** | Nest tsc, assets, watch/debug, existing test transforms сохранить |
|
||||
| DEPS-003: driver alignment | Runtime packaging, native maintenance, consistent SQL verification | 2 resolved versions установлены; image bytes/runtime improvement **not measured** | SQLite integrity/durability/transactions, ABI и migration CLI сохранить |
|
||||
| DEPS-004: debug mode | Local troubleshooting/cognitive load | Различие launch config доказано; recovery time **not measured** | Production cookie/Swagger policy не ослаблять |
|
||||
|
||||
Не обещаются проценты ускорения, bundle reduction или денежная экономия. Package count не равен bundle size: dev dependencies frontend отсутствуют в nginx runtime, а platform lock entries не все устанавливаются на одном host. Общие пакеты между repos не означают двойной runtime bundle.
|
||||
|
||||
## 7. Top actions и план безопасной консолидации
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **DEPS-001:** исправить самостоятельный backend quick start существующим preparation command; сверить его с tracked generator/import chain. Самая дешёвая правка с непосредственной setup value.
|
||||
2. **DEPS-002:** удалить direct ts-loader/source-map-support в отдельной dependency task; штатно обновить lockfile, затем clean-install/peer/compiler/assets checks. Не удалять вручную Webpack nodes из node_modules/lockfile.
|
||||
3. **DEPS-004:** согласовать debug launch с start:dev одной script value, сохранив production mode.
|
||||
|
||||
### Решение, требующее тестового подтверждения
|
||||
|
||||
4. **DEPS-003:** выбрать adapter-compatible SQLite driver version, clean resolve, затем SQLite/image/durability proof. Новая ADR нужна только если принято намеренно сохранять разные driver generations или менять runtime DB packaging; для обычного version alignment достаточно dependency diff и regression evidence.
|
||||
|
||||
Минимальная последовательность future implementation: один связанный manifest/config diff → canonical lockfile update штатным npm → чистая установка на нужной платформе → `npm ls --all` → соответствующие existing type/build/tests → image/durability только для native change. После dependency changes выполнить repo-required `npm audit` и отдельно triage, не принимать `--force`/`--legacy-peer-deps`. Эти операции **не выполнялись в аудите**.
|
||||
|
||||
Не предлагается миграция в monorepo, центральный dependency manifest, новый env parser, custom unused-dependency script, общий test runner или объединение ESLint/graph/contract validators.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **Audit 11:** `backend/package.json:34`, `.gitea/workflows/verify.yml:83–89` — preparation ownership standalone verify/CI; здесь зафиксирован fresh local setup, execution repetition не пересчитывался.
|
||||
- **Audit 07:** `frontend/src/shared/api/client.ts:15–18`, `backend/src/integrations/tbank/tbank.client.ts:49–54` — согласование end-to-end timeout/retry budget; одинаковые значения не требуют shared constant автоматически.
|
||||
- **Audit 07/15:** `frontend/src/app/ui/App.tsx:13–19` и `.ai/conventions/data.md:70–74` — focus-refetch behavior отличается от описанной policy; не включено в dependency findings.
|
||||
- **Audit 13:** `backend/package.json:107–121`, `frontend/package.json:118–126`, `docs/package.json:24–33`, `backend/.gitea/workflows/verify.yml:64–65` — overrides/advisory exception требуют отдельного security rationale; vuln scan не выполнялся.
|
||||
- **Audit 15:** `specs/README.md:28–31`, `specs/package.json:15–21` — ownership/version of external OpenSpec CLI; SDD lifecycle и CLI installation state здесь не исследованы.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Самый дешёвый proof | Что изменит |
|
||||
| --- | --- | --- |
|
||||
| Есть ли поддерживаемый backend Webpack consumer вне tracked scripts/config/CI? | Уточнить у owner конкретную command/config; project search не нашёл | При реальном consumer сохранить ts-loader либо оформить его явный режим вместо DELETE |
|
||||
| Почему direct SQLite v13 нужна при adapter v12? | Проверить необходимость v13 API в двух SQL consumers и существующим image/durability proof на compatible v12 | Если специфичный v13 contract нужен, DEPS-003 станет INVESTIGATE до официального adapter support |
|
||||
| Сколько transitive nodes/bytes действительно исчезнет после DEPS-002/003? | Сравнить новый штатный lock graph и image digest/artifact в будущей dependency task | Уточнит benefit; не меняет доказанный unused route, не требует нового profiler |
|
||||
| Работает ли самостоятельный свежий setup после исправления инструкции? | Отдельный disposable checkout/SQLite fixture без inherited generated output | Позволит заменить статический inference DEPS-001 на runtime-verified statement |
|
||||
| Нужна ли воспроизводимая native OpenSpec workflow на clean host? | Уточнить requirement и получить фактический `openspec --version` owner среды | Только тогда решить local pin; application setup от этого не зависит |
|
||||
| По-прежнему ли Sonar требует отдельный старый TS profile? | Проверить version существующего server analyzer / один уже выполненный scan log | При современном analyzer можно пересмотреть profile, сейчас KEEP |
|
||||
|
||||
Открытые вопросы не являются блокерами read-only отчёта. Ни установка новых tools, ни runtime database access для завершения аудита не нужны.
|
||||
|
||||
## 10. Проверка итогового артефакта
|
||||
|
||||
Единственная запись — `audit-results/12-dependencies-and-configuration-report.md`, вне четырёх самостоятельных repos и вне root allowlist. Выполнена semantic сверка: scope, точные SHA/строки, все четыре finding fields, counterarguments, preserved invariants, отсутствие выдуманных измерений, четыре специальные matrices/registers и consolidation table.
|
||||
|
||||
- Повторные `git status --short --branch` и `git rev-parse HEAD` пяти repos подтвердили чистые working trees и те же SHA, что в metadata.
|
||||
- В пяти repos `git diff --check` и `git diff --cached --check` завершились с exit 0 без diagnostics; staged/unstaged tracked изменений нет.
|
||||
- `git diff --no-index --check /dev/null audit-results/12-dependencies-and-configuration-report.md` завершился с exit 0 без diagnostics: проверен сам новый ignored отчёт, а не только пустой tracked diff.
|
||||
- `git status --short --ignored -- audit-results/12-dependencies-and-configuration-report.md` показал `!! audit-results/`: отчёт сохраняется локально и не добавлен в root tracking.
|
||||
- Application tests/build/clean install не выполнялись; успешная проверка артефакта не подменяет future regression proof рекомендаций.
|
||||
|
||||
## Consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| DEPS-001 | FIX | P2 | High | backend | README.md; package.json; prisma/schema.prisma; src/prisma/prisma.service.ts | Самостоятельный fresh quick start пропускает generated client prerequisite | S | DEPS-004 |
|
||||
| DEPS-002 | DELETE | P2 | High | backend | package.json; package-lock.json; nest-cli.json | Неиспользуемый ts-loader удерживает Webpack peer chain и лишние direct declarations | S | DEPS-001, DEPS-004 |
|
||||
| DEPS-003 | SIMPLIFY | P2 | High | backend | package.json; package-lock.json; test/container-smoke.cjs; test/bank-cards-durability.cjs | Runtime и SQL checks удерживают две major-версии SQLite-драйвера | M | DEPS-002 |
|
||||
| DEPS-004 | FIX | P3 | High | backend | package.json; .env.example; src/main.ts; src/config/swagger.config.ts; src/modules/auth/presentation/auth.controller.ts | Debug launch без NODE_ENV development выбирает другую HTTP mode policy | S | DEPS-001, DEPS-002 |
|
||||
@@ -0,0 +1,355 @@
|
||||
# Аудит 13 — Reliability & Security
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Audit ID:** 13, Reliability & Security.
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Workspace:** `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
- **Режим:** пассивный read-only аудит исходников, конфигураций, существующих тестов и behavioral specs. Единственный созданный файл — этот отчёт вне четырёх исследованных репозиториев.
|
||||
|
||||
| Repo | Найденный путь относительно workspace | Точный HEAD | Начальный `git status --short` |
|
||||
| --- | --- | --- | --- |
|
||||
| backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | Пустой, дерево чистое |
|
||||
| frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | Пустой, дерево чистое |
|
||||
| specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | Пустой, дерево чистое |
|
||||
| docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | Пустой, дерево чистое |
|
||||
| workspace root | `./` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | Пустой, дерево чистое |
|
||||
|
||||
Прочитаны собственные `AGENTS.md` всех четырёх репозиториев и применимые локальные правила. Правила использовались для навигации, а не как доказательство корректности или необходимости механизма.
|
||||
|
||||
**Skills:** установленный `ponytail-audit` найден и прочитан. По его фактическому контракту security/correctness находятся вне его scope: он применён только к вопросу безопасного удаления/упрощения защит. Такие удаления не обоснованы; число удаляемых строк/dependencies не оценивалось. Security-проход выполнен по `security-review` с трассировкой входов, проверкой ownership и контраргументов. `graphify` прочитан, существующий `graphify-out/graph.json` не найден; построение графа и установка пакетов не выполнялись из-за read-only ограничения.
|
||||
|
||||
**Предыдущий аудит:** `audit-results/technical-audit-2026-10-11.md` использован как реестр гипотез. Выводы ниже заново проверены по указанным исходникам и вызывающим путям. Совпадающие гипотезы отмечены в Related IDs, но прежний отчёт не служит доказательством.
|
||||
|
||||
**Что запускалось:** `git status --short`, `git rev-parse HEAD` в root и четырёх repos; точечные файловые/content searches; два статических подсчёта контроллеров и HTTP-декораторов через `rg`. При завершении по `verification-before-completion` — повторные Git status и `git diff --check`, просмотр нового отчёта, `git check-ignore` для его пути и `git diff --no-index --check /dev/null audit-results/13-reliability-and-security-report.md` для whitespace нового ignored файла.
|
||||
|
||||
**Что не запускалось:** приложение, HTTP-запросы, payload attacks, реальные T-Bank/MOEX/NCR API, тесты, build, full-suite/verify, install, codegen, миграции, dependency/secret scanners, чтение реальной БД и env dump. Тесты ниже **прочитаны, но не исполнены**. Runtime-воспроизведение и фактическая конфигурация production — `UNVERIFIED`.
|
||||
|
||||
Root `.gitignore:1–37` игнорирует `audit-results/`; allowlist не менялся. `deploy/` обнаружен как дополнительный самостоятельный checkout и не включён в аудит четырёх repos. Для deployment рассмотрены только предоставленный root `compose.yaml`, frontend Nginx config и тематическая docs.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт / P1:** ошибка persistence в дочерней работе или завершении hourly может оставить durable `running`. Координатор ждёт без конечной границы, а следующий обход того же процесса отклоняется. Это наиболее важный отказ восстановления, потому что затрагивает дальнейшую свежесть нескольких финансовых ресурсов — RISK-001.
|
||||
2. **Факт / P2:** исключение общего уровня обхода не добавляется в список failures и может завершиться как `succeeded`. Сохранённые данные при этом не обязательно обновлены — RISK-002.
|
||||
3. **Факт / P2:** выбор fallback прогнозов способен блокировать новый полный результат для изменившегося окна; календарь облигаций после первой загрузки вообще не освежается обычным hourly-путём. Это два независимых механизма сохранения устаревших выплат — RISK-003/004.
|
||||
4. **Факт / P2:** основной экран счёта теряет уже подготовленную metadata портфеля и скрывает failure фонового чтения. Пользователь не получает необходимого контекста сохранённых сумм — RISK-005.
|
||||
5. **Факты / P2:** online instrument paths превращают постоянную ошибку брокерского token в `500`; нижняя граница пароля имеет разные единицы frontend/backend. Оба дефекта имеют локальные исправления у существующих владельцев — RISK-006/007.
|
||||
6. **Сохранённые гарантии:** в выбранных потоках не найден подтверждённый IDOR, SQL injection, XSS или передача брокерского token в публичный ответ. Ownership, generation/attempt fences, banking CAS/fingerprint/tombstones и точная денежная арифметика нужны. Это вывод о проверенной выборке, а не сертификат отсутствия уязвимостей во всей системе.
|
||||
7. **Риск / `UNVERIFIED`:** в приложении и поставляемом Nginx нет ограничения публичных login/register попыток; внешний HTTPS proxy может закрывать этот риск, но его конфигурация не предоставлена — RISK-008. Подтверждённых P0, эксплуатаций и измерений ущерба нет.
|
||||
|
||||
Итого: **7 дефектов по коду** — один P1 и шесть P2; **1 условный security risk** с Decision `INVESTIGATE`. Основная работа — корректные failure states и recovery, а не сокращение защит или внедрение нового framework.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Фактический стек и метод подсчёта
|
||||
|
||||
- Backend: NestJS/TypeScript, Prisma с `adapter-better-sqlite3`, локальная SQLite, bcrypt, JWT, Pino, T-Bank gRPC; MOEX/NCR HTTP — `backend/package.json:40–67`, `backend/src/prisma/prisma.service.ts:12–30`, `backend/prisma/schema.prisma:1–9`.
|
||||
- Frontend: React/Vite SPA, TanStack Query, generated OpenAPI transport, auth в памяти; PWA cache только статической оболочки — `frontend/package.json:39–62`, `frontend/vite.config.ts:45–51`.
|
||||
- Найдено **11 controller source files и 52 HTTP method declarations**. Метод: из `backend/` выполнены `rg --files src/modules -g '*.controller.ts' | wc -l` и `rg --count '^\s*@(Get|Post|Put|Patch|Delete)\(' src/modules -g '*.controller.ts'`; второй результат суммирован: `1+2+1+10+2+3+8+2+14+5+4=52`. Это source inventory, не runtime route count; Swagger/framework routes и внешние proxies не включены.
|
||||
- При просмотре guards все 48 прикладных declarations защищены class-/method-level `JwtAuthGuard`; четыре намеренно публичных — register, login, refresh, logout. Наличие guard отдельно не принималось за доказательство object authorization.
|
||||
|
||||
### 3.2. Asset / threat map
|
||||
|
||||
| Asset | Actor / capability | Entry point | Значимая угроза | Проверенный владелец / controls |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| User credentials, Session | Неаутентифицированный клиент; владелец действующей cookie | `/auth/register`, `/login`, `/refresh`, `/logout` | Подбор, replay, конкурирующая rotation, утечка | Runtime DTO, bcrypt, generic login error, HS256/type/TTL, refresh hash CAS, cookie boundary |
|
||||
| Brokerage credentials | Авторизованный User; поздний RPC прежнего подключения | PUT/DELETE `/tbank-connection` | Plaintext exposure, повреждение рабочего подключения, late writes | Проверка token до transaction, AES-GCM, атомарная замена generation, каскады |
|
||||
| Accounts, portfolio, value history | User с чужим accountId или старой generation | Brokerage account reads / background publication | IDOR, раскрытие прежних данных, ложная свежесть | User/revision/account-scoped queries, подтверждённая membership, conditional publish |
|
||||
| Bank cards, operations, refunds | User с чужими nested IDs; retry/lost response; competing writers | `/bank-cards/**` | Cross-user links, double effect, lost update, refund overrun | Owner checks внутри transaction, client UUID + fingerprint, version CAS, tombstone, BigInt |
|
||||
| Ledger / income | User с чужим/устаревшим cursor; malformed upstream page | Operations/income GET, staged publication | Перескок owner/filter, частичный reconciliation | HMAC cursor + binding, scoped reads, stage markers, atomic publication |
|
||||
| Public catalog, screener artifacts | User с чужим searchId; отказ token producer | Stocks/bonds search/detail/company | Межпользовательский результат, error poisoning, лишние retries | Projection, searchId owner/revision/TTL, credentials separated from shared public rows |
|
||||
| Stored forecasts / durable work | Ошибка сети/SQLite, смена membership, process crash | Hourly walk, forecast resolution | Неверные суммы, устаревший forecast, вечный running | Dependency versions, transactional fences, processRunId; найденные пробелы RISK-001–004 |
|
||||
| Logs / backup | Оператор; сторонний читатель логов/копий | HTTP completion, application events, recovery runbook | Secrets/PII exposure, утрата ключа/данных | Safe completion fields, redaction, allowlist, отдельный encryption key; live storage не проверено |
|
||||
|
||||
### 3.3. Threat Boundary Matrix — семь прослеженных семейств потоков
|
||||
|
||||
| Flow | Вход → обязательная boundary → consumer | Проверка nested ownership / replay | Результат |
|
||||
| --- | --- | --- | --- |
|
||||
| 1. Login / refresh / logout | AuthController → CredentialsDto/AuthService → Session и AuthProvider/shared fetch | JWT algorithm/type, hash rotation; неуспешный refresh не пишет cookie | CAS сохранить; password mismatch RISK-007; perimeter RISK-008 |
|
||||
| 2. Replace / delete connection | Controller `request.user.sub` → TbankConnectionService → token validator/crypto → SQLite transaction → frontend reset | Client userId не authority; old generation каскадно удаляется; неудачная validation сохраняет старую connection | Проверенные fences сохранить |
|
||||
| 3. Portfolio / events / all | Guard + user.sub → `access.local(userId, accountId)` → scoped repositories/ForecastService → AccountPage | `all` ограничен membership пользователя; forecasts scoped user+revision; нет fallback другого owner | RISK-003/004/005; confirmed IDOR path не найден |
|
||||
| 4. Ledger / income / sync status | Controllers → access.local → scoped readPage и cursor HMAC/binding → durable work status | Binding включает userId, revision, account revisions и filters; sync status читает user/revision/id | Authorizing checks сохранить; read-owner drift — cross-audit signal |
|
||||
| 5. Banking create / transfer / refund / update / delete | Guard → DTO/input → BankCardsRepository.write → owner lookups + CAS + DB constraint → explicit DTO | Проверены source/target card, category, original expense, duplicate UUID другого owner, deleted replay | Равносильной замены fingerprint/CAS/tombstone не найдено; KEEP |
|
||||
| 6. Instrument search / detail / screener | Guard → current/onlineToken → fixed upstream → public projection; searchId → owner+revision+TTL | Public instrument sources отделены от personal holdings; screener pages проверяют User и generation | RISK-006; shared public cache сам по себе не утечка |
|
||||
| 7. Hourly work / publication / restart | Native timer → reserveHourly → membership/portfolio/operations/events owners → conditional settle | AttemptId/processRunId и transaction fences; stale publication не становится актуальной | RISK-001/002; multi-process lease пока не нужен при одном процессе |
|
||||
|
||||
**Основные evidence для матрицы:** `backend/src/modules/auth/application/auth.service.ts:64–140,166–201`; `backend/src/modules/auth/presentation/auth.controller.ts:81–137`; `backend/src/modules/investments/tbank-connection/tbank-connection.controller.ts:32–56`; `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31–88`; `backend/src/integrations/tbank/tbank-access.service.ts:148–237,262–288`; `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:82–148`; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:347–388`; `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:313–389`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:94–240,251–330`; `backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:65–109`.
|
||||
|
||||
### 3.4. Репрезентативность и пределы
|
||||
|
||||
Выборка risk-based: все controller guards просмотрены, а детальная трассировка ограничена семью семействами выше и их общими security/persistence boundaries. Прочитаны репрезентативные configured HTTP/SQLite tests: auth rotation/storage failures, banking foreign links/refund race/replay, forecast filtering/late calendar; наличие test source не доказывает его прохождение.
|
||||
|
||||
Pattern searches в frontend `src/**/*.{ts,tsx}` по `dangerouslySetInnerHTML|innerHTML|eval\(|new Function|localStorage|sessionStorage` и backend `src/**/*.ts` по unsafe/raw SQL, `child_process`, `execSync`, `eval` совпадений не дали. Это ограниченный inventory потенциальных sinks, не автоматический SAST и не доказательство безопасности всех данных.
|
||||
|
||||
Вне покрытия: dependency CVE audit, все финансовые формулы/инструменты, вся active OpenSpec history, CI/tooling, реальные secrets и DB, серверные права/HTTPS/WAF, live restore, raw/generated artifact scans. Отсутствие `graphify` graph и информации о внешнем proxy ограничивает только соответствующие способы подтверждения, не отменяет source findings.
|
||||
|
||||
## 4. Security & Reliability Findings
|
||||
|
||||
### RISK-001 — Durable `running` не имеет гарантированного выхода после persistence failure
|
||||
|
||||
- **Decision = FIX; Severity = P1; Confidence = High; Effort = M.** Тип: дефект recovery по коду; частота SQLite failures `UNVERIFIED`.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–189,197–218`; `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:190–201,223–242`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:563–593`; `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:195–230,296–301`.
|
||||
- **Claim:** кратковременная ошибка publish/finish/fail либо settle может закончить process-level работу, оставив durable row `running`. Hourly waiter не имеет deadline; `reserveHourly` не заменяет такой row в том же processRunId.
|
||||
- **Current mechanism:** events refresh возвращает work IDs; `settledWorks` опрашивает их каждые 100 ms до выхода из `running`. Общий catch enrichment очищает completions/active/queue, но не переводит abandoned rows в terminal state. `settleHourly(...).catch(() => undefined)` также подавляет ошибку записи.
|
||||
- **Threat model:** свежие personal datasets → storage failure/прерванный worker → publish/finish или settle → предположение «каждый running обязательно завершится» → бесконечное ожидание и запрет следующего обхода → existing attempt/generation fences → bounded observation и согласованный recovery.
|
||||
- **Impact / actual cost:** автоматическое обновление connection может остановиться до restart или иного пути, заново резервирующего дочернюю работу. Также остаётся периодический DB polling. **Impact unmeasured**: число затронутых connections, длительность и DB/runtime cost не измерены.
|
||||
- **Why now:** hourly — текущий владелец обновления personal datasets; пассивный portfolio GET не исправляет lifecycle. Если stuck row принадлежит hourly, даже восстановление SQLite само по себе его не освобождает.
|
||||
- **Minimum change:** ограничить ожидание дочерних attempts; различать завершённую и abandoned работу; при восстановившемся storage условно завершать/перерезервировать запись по исходному attemptId. Ошибку самого settlement сделать наблюдаемой и повторно сверять её на штатном tick. Одного дополнительного UPDATE в catch недостаточно, если SQLite ещё недоступна.
|
||||
- **Guarantees at risk:** timeout не равен cancellation RPC. При освобождении work поздний ответ должен проиграть attempt/generation fence; старый child не должен завершить новый attempt. Сохранённые успешные snapshots нельзя удалять ради выхода из running.
|
||||
- **Counterarguments / trade-offs:** перезапуск помогает благодаря processRunId, а новый data request иногда восстановит child; это не гарантированное восстановление в живом процессе. Межпроцессный lease проблему одного процесса не решает. Ограничение observation должно учитывать допустимую длительность большого портфеля.
|
||||
- **Verification or rollback:** isolated SQLite regression с однократным отказом `publishSource`/`finish` и отдельно `settleHourly`: storage восстанавливается, row выходит из running, следующая eligibility разрешает новую работу, late publication старого attempt отклоняется. Существующие suites hourly не проверяют: `backend/test/upcoming-events.e2e-spec.ts:140–147` подменяет scheduler. Runtime proof сейчас `UNVERIFIED`; rollback локальных lifecycle changes не должен менять schema/data.
|
||||
- **Related finding IDs:** RISK-002, RISK-004; прежняя гипотеза technical F2 заново подтверждена.
|
||||
|
||||
### RISK-002 — Общая ошибка обхода может публиковаться как полный успех
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.** Тип: code-level correctness bug статуса.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:93–110,113–125,145–151,178–190`; `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:32–56,245–271`; consumer `backend/src/modules/investments/portfolio/application/portfolio.service.ts:55–75,207–229`.
|
||||
- **Claim:** exception из `membership.request`, `workError`, `access.local` или orchestration попадает в общий catch без добавления failure. Пустой `failures` становится `succeeded`, даже когда обязательные шаги не выполнены.
|
||||
- **Current mechanism:** итог выводится из длины failures и счётчика attempted, а не из достигнутого завершения обязательного обхода. Например, `membership.request` может бросить до первого increment; catch вызывает `settle([], 0)` → `succeeded`.
|
||||
- **Threat model:** trustworthy freshness/outcome → DB exception → walk catch → отсутствие локального failure ошибочно принимается за успех → клиент получает ложный `lastAttempt` → existing безопасные public codes → явно unsuccessful aggregate outcome.
|
||||
- **Impact / actual cost:** пользователь и диагностика видят успешный обход со старыми либо отсутствующими данными. **Impact unmeasured**; payload не обязательно повреждён — дефект в достоверности статуса.
|
||||
- **Why now:** этот status переносится в metadata нескольких финансовых resources; он нужен для понимания ошибок обновления.
|
||||
- **Minimum change:** общий catch обязан явно фиксировать безопасную причину и failed/partial outcome; считать skipped mandatory sections незавершёнными. Использовать текущие progress/sections и logger, не вводить новый status framework.
|
||||
- **Guarantees at risk:** успешные account snapshots должны сохраняться при частичной ошибке. Смена/deletion connection — interruption, а не ложная проблема новой generation. Не выводить raw exception/message в public sections.
|
||||
- **Counterarguments / trade-offs:** локальные operations/events catches уже добавляют failures и работают лучше; они не защищают исключения вне своих блоков. `attempted` не доказывает completion и может давать partial при фактическом отсутствии выполненных обязательных секций.
|
||||
- **Verification or rollback:** controlled orchestration case с reject membership reservation до increment и с reject `workError` после завершённого child: `lastAttempt` не succeeded, сохранённый payload неизменен. Проверить отдельный partial-success case. Runtime proof `UNVERIFIED`.
|
||||
- **Related finding IDs:** RISK-001, RISK-005; прежняя technical F2.
|
||||
|
||||
### RISK-003 — Overlapping fallback блокирует публикацию нового полного forecast
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = M.** Тип: code-level correctness bug и расхождение behavioral requirement.
|
||||
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79,80–174`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:152–173,271–279`; `backend/src/modules/investments/portfolio/application/forecast.service.ts:35–94,98–123`; `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:10–24,63–76`.
|
||||
- **Claim:** при новом scopeKey и существующем пересекающемся forecast `if (!previous && fallback) return fallback` возвращает старый payload даже при полном новом candidate. Ежедневный сдвиг default window — обычный достижимый путь, не искусственная атака.
|
||||
- **Current mechanism:** accountId/from/to входят в persisted identity. Без exact row ищется первый overlap без `orderBy`; новый candidate не достигает dependency validation/upsert. При отсутствии candidate и непересекающемся окне последний forecast также не выбирается, хотя spec требует успешный пустой фильтр сохранённого результата.
|
||||
- **Threat model:** прогнозные суммы и исходное окно → нормальная смена UTC дня/новых holdings → events.get/resolve → fallback принят за достаточную замену нового полного расчёта → сохранение прежних выплат → transactional version checks → отделить публикацию полного результата от фильтрации чтения.
|
||||
- **Impact / actual cost:** пользователь может продолжать получать старое количество бумаг/суммы после обновления sources; out-of-window read без candidate может вернуть `data: null` вместо сохранённого пустого результата. **Impact unmeasured**.
|
||||
- **Why now:** окно меняется штатно, а fallback является основной защитой последнего полного прогноза при failures. Ошибка находится внутри этой важной защиты.
|
||||
- **Minimum change:** у существующих ForecastService/Repository развести выбор последнего полного результата конкретного owner/account и фильтрацию request window. Полный новый hourly-расчёт должен публиковаться, неуспех — оставлять прежний. Задать детерминированный порядок выбора сохранённого forecast.
|
||||
- **Guarantees at risk:** не заменять полный прогноз частичным; сохранить его исходные `calculatedAt`, dependency revisions и окно при простом фильтрующем GET. Membership/portfolio/source CAS checks необходимы. Простого удаления строки 79 без проверки read semantics недостаточно.
|
||||
- **Counterarguments / trade-offs:** fallback нужен; существующий `backend/test/upcoming-events.e2e-spec.ts:619–640` правильно требует сохранение timestamp при узком read window. Он не проверяет новый полный candidate после смены holdings и окна. Само пересечение не доказывает актуальность расчёта.
|
||||
- **Verification or rollback:** isolated repository/service cases: старый window + новый полный candidate с изменившимся количеством; narrow read без новой публикации; failed refresh; несколько сохранённых windows; пустое пересечение. Проверить суммы, `calculatedAt`, выбранную revision и сохранность rollback, а не число DB calls. Runtime proof `UNVERIFIED`.
|
||||
- **Related finding IDs:** RISK-004, RISK-005; прежняя technical F1.
|
||||
|
||||
### RISK-004 — Hourly не обновляет сохранённые event calendars
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.** Тип: дефект refresh policy.
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:166–175`; `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:182–239`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:113–125,152–158,164–193,607–623,679–692`; `specs/openspec/specs/hourly-investment-sync/spec.md:8–9`.
|
||||
- **Claim:** hourly events.refresh идёт через readMany/requestMany с `force=false`; dividends/bond-events имеют `stable` policy. Если row уже существует, её наличие считается достаточным, независимо от времени наблюдения.
|
||||
- **Current mechanism:** обновление по TTL есть только у quote kinds. `bond-events` не имеет rangeKey, поэтому может остаться на первом успешном ответе бессрочно. Dividends получают новый key при изменении окна/дня и могут загружаться заново тогда, но изменение выплаты внутри прежнего окна не подхватывается последующими часовыми обходами.
|
||||
- **Threat model:** полнота ожидаемых выплат → изменившиеся данные provider → hourly calendar refresh → календарь ошибочно считается неизменяемым справочником → новые/отменённые выплаты не отражены → versioned source publication → целевой hourly refresh calendars.
|
||||
- **Impact / actual cost:** обещанный обход выполняется без RPC за актуальным календарём; RISK-003 может затем отдельно заблокировать и новый forecast. **Impact unmeasured**; дополнительные необходимые RPC тоже пока не измерены.
|
||||
- **Why now:** portfolio holdings и payout calendars — изменяемые источники, на которых построены пользовательские прогнозы.
|
||||
- **Minimum change:** передавать существующий force именно для event-calendar sources в hourly-пути либо задать им обоснованный ограниченный TTL. Стабильные metadata переиспользовать; обычный GET оставить пассивным. Не force-обновлять все kinds ради двух календарей.
|
||||
- **Guarantees at risk:** rate budget, one producer/observer coordination, сохранение старого полного calendar при malformed/error и late generation fence.
|
||||
- **Counterarguments / trade-offs:** public shared calendar cache полезен и не требует удаления. Daily key частично маскирует проблему dividends, но не решает intraday изменения и постоянный bond-events key. Force всех instrument sources создаст ненужный traffic.
|
||||
- **Verification or rollback:** два controlled hourly refresh одной connection и окна с разными provider responses; второй calendar действительно обновляет revision и результат. Fail второго запроса сохраняет первый; обычный GET не делает RPC. Runtime proof `UNVERIFIED`.
|
||||
- **Related finding IDs:** RISK-001, RISK-003; прежняя technical F3.
|
||||
|
||||
### RISK-005 — AccountPage скрывает свежесть portfolio и failed background read
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.** Тип: failure-state UX / достоверность финансового представления.
|
||||
- **Evidence:** `frontend/src/entities/portfolio/api/usePortfolio.ts:19–25`; `frontend/src/shared/api/resource-query.ts:129–147`; `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:86–101,125–135`; `frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:24–34`; `frontend/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx:55–59`; `frontend/src/shared/ui/SourceSyncStatus/SourceSyncStatus.tsx:23–47`; test consumer `frontend/src/pages/account/ui/AccountPage/AccountPage.test.tsx:751–855`.
|
||||
- **Claim:** VM возвращает `source: portfolio.meta`, но container не передаёт source в loaded view. При cached data transport error не включается в loaded presentation и не даёт retry.
|
||||
- **Current mechanism:** query wrapper предоставляет data/meta/error раздельно. SourceSyncStatus уже умеет показывать timestamp, stale и lastAttempt, но получает undefined. VM сохраняет loaded при наличии data; это полезно, однако ошибка остаётся невидимой.
|
||||
- **Threat model:** смысл сохранённых финансовых сумм → upstream/read failure → cached Query → metadata потеряна на VM→view boundary → старые суммы показаны без freshness/error context → existing status component → передать metadata и локальный read error.
|
||||
- **Impact / actual cost:** пользователь не видит возраст данных и неудачное перечитывание; риск решений по устаревшим суммам. **Impact unmeasured**. Чужие данные этим дефектом не раскрываются.
|
||||
- **Why now:** silent stale UI усиливает последствия RISK-001–004. Тест явно закрепляет отсутствие уведомлений, поэтому обычный зелёный suite не устранит проблему.
|
||||
- **Minimum change:** передать существующий source prop; добавить безопасный local background-read error/retry у нынешнего page owner. Отдельно сохранить neutral first-load и access-failure path.
|
||||
- **Guarantees at risk:** не стирать cached snapshot из-за transient failure, не показывать прежнего owner после auth/connection switch, не запускать personal sync кнопкой повторного чтения. Status должен быть доступным текстом/accessible name.
|
||||
- **Counterarguments / trade-offs:** тихое сохранение payload предотвращает flash/error page, но сохранение и отсутствие failure context — разные решения. Частичный snapshot уже отображается отдельно и не заменяет freshness.
|
||||
- **Verification or rollback:** existing route test с stale/failed `meta` и MSW background `503`: старые суммы остаются, timestamp/предупреждение видны, retry выполняет GET; успешный refetch снимает error. Runtime/browser proof `UNVERIFIED`.
|
||||
- **Related finding IDs:** RISK-002, RISK-003, RISK-004; прежняя technical F5.
|
||||
|
||||
### RISK-006 — Invalid broker token в online paths превращается в retryable `500`
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.** Тип: error mapping / recovery defect.
|
||||
- **Evidence:** `backend/src/integrations/tbank/tbank.client.ts:527–565`; `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:143–165,227–249,295–317`; `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:241–267`; `backend/src/integrations/tbank/tbank-token.service.ts:26–35`; `backend/src/common/filters/http-exception.filter.ts:19–31`; consumer `frontend/src/shared/api/client.ts:127–151,162–193`.
|
||||
- **Claim:** если сохранённый token внешне отозван и ещё не помечен `invalidToken`, online RPC бросает обычный Error с внутренним code; online application path не переводит его в публичный `INVALID_TBANK_TOKEN`.
|
||||
- **Current mechanism:** background owners умеют terminal invalid-token handling; connection.put использует `translateInvalidToken`. Online search/detail/company вызывают client напрямую. Filter безопасно скрывает Error, но выдаёт `500 INTERNAL_SERVER_ERROR`, который frontend считает transient.
|
||||
- **Threat model:** корректное recovery connection → внешнее revoke credentials → online GET → постоянная ошибка принята за внутренний transient failure → повтор заведомо неуспешного RPC/неверное сообщение → existing translator → применить перевод на online boundary.
|
||||
- **Impact / actual cost:** пользователь теряет recovery code. Для одного GET frontend допускает до **трёх HTTP попыток**: две итерации `RETRY_DELAYS_MS` и finalAttempt; это верхняя граница по коду, не измеренный traffic. Реальное amplification зависит от caller/provider. **Impact unmeasured**.
|
||||
- **Why now:** online reference paths реализованы и доступны через защищённые controllers; background handling не защищает эти calls.
|
||||
- **Minimum change:** переиспользовать текущий invalid-token translator у online application owner. Сохранить отдельные outcomes transient unavailable / not found. Решение о durable invalidToken flag должно быть generation-aware и не требовать записи без captured revision.
|
||||
- **Guarantees at risk:** не отдавать raw provider error; не пометить новое подключение ошибочным из-за позднего failure старого token; не убрать bounded network retry для действительно transient errors.
|
||||
- **Counterarguments / trade-offs:** `onlineToken` уже возвращает правильный code, если invalidToken flag выставлен; defect существует до обнаружения revoke background owner. `500` не раскрывает secret — это не data leak, а нарушение классификации.
|
||||
- **Verification or rollback:** controlled online endpoint с provider `UNAUTHENTICATED`/`PERMISSION_DENIED`: публичный `400 INVALID_TBANK_TOKEN`, frontend GET не повторяется; transient upstream по-прежнему даёт `503`. Runtime proof `UNVERIFIED`.
|
||||
- **Related finding IDs:** RISK-005; прежняя technical F13.
|
||||
|
||||
### RISK-007 — Нижняя граница пароля проверяется в разных единицах
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.** Тип: runtime validation / UX correctness, не password bypass.
|
||||
- **Evidence:** `backend/src/modules/auth/presentation/auth.dto.ts:43–54,66–74`; `frontend/src/features/auth/model/auth.schema.ts:36–43`; `specs/openspec/specs/user-authentication/spec.md:10,109–110`; `docs/domains/authentication/overview.md:12–21`.
|
||||
- **Claim:** frontend и behavioral spec требуют 12–72 UTF-8 bytes; backend minimum — MinLength(12), maximum — UTF-8 bytes. Для шести кириллических букв frontend minimum выполняется, backend minimum — нет.
|
||||
- **Current mechanism:** единственный backend byte validator проверяет только максимум. `CredentialsDto` общий для login и registration; расхождение docs уже отмечает, но правило runtime остаётся несогласованным.
|
||||
- **Threat model:** доступный auth flow → пользователь с не-ASCII password → frontend schema / server DTO → единицы длины ошибочно считаются одинаковыми → API отвергает принятый формой ввод → existing backend validator → единая policy на runtime boundary.
|
||||
- **Impact / actual cost:** лишний запрос и непредсказуемый отказ формы; потенциальная несовместимость policy для существующих credentials. **Impact unmeasured**. Более строгий minimum не является подтверждённой эксплуатацией.
|
||||
- **Why now:** это явно сохранённое требование, а не рекомендация внешнего checklist о более длинном пароле.
|
||||
- **Minimum change:** проверять обе byte bounds в текущем PasswordBytesConstraint и согласовать minimum metadata. Если нужно изменить саму password policy — сначала отдельное решение владельца, затем согласованный frontend/backend contract.
|
||||
- **Guarantees at risk:** верхние 72 bytes защищают от bcrypt truncation; пароль нельзя trim/normalize. Нельзя silently сделать существующий password непригодным для login при ужесточении registration policy.
|
||||
- **Counterarguments / trade-offs:** byte minimum допускает меньше видимых не-ASCII символов; это уже выбранный spec contract, а не автоматически лучшая security policy. Изменение policy не следует маскировать исправлением единиц.
|
||||
- **Verification or rollback:** existing configured HTTP validation boundary и frontend schema: ASCII и не-ASCII на 11/12/72/73 bytes, сохранение пробелов в password. Runtime proof `UNVERIFIED`.
|
||||
- **Related finding IDs:** RISK-008; прежняя technical F12.
|
||||
|
||||
### RISK-008 — Защита public auth от перебора и resource exhaustion не подтверждена
|
||||
|
||||
- **Decision = INVESTIGATE; Severity = P2; Confidence = Medium; Effort = S.** Тип: architecture/deployment risk; **UNVERIFIED для фактической публичной установки**.
|
||||
- **Evidence:** `backend/src/modules/auth/presentation/auth.controller.ts:55–79`; `backend/src/modules/auth/application/auth.service.ts:19–26,35–78`; `backend/src/main.ts:19–41`; `backend/src/app.module.ts:18–35`; `frontend/docker/nginx.conf:20–33`; `docs/domains/authentication/overview.md:89–95`; root `compose.yaml:40–43`.
|
||||
- **Claim:** app-level ограничение auth attempts не реализовано; поставляемый Nginx proxy также не содержит его. Можно подтвердить отсутствие защиты в этой цепочке, но нельзя по ней подтвердить доступность обходного public пути или отсутствие защиты внешнего HTTPS proxy.
|
||||
- **Current mechanism:** public login всегда выполняет bcrypt compare, включая dummy hash неизвестного email; register нового email выполняет bcrypt hash. Source inventory не обнаружил auth throttler/global guard. Frontend block submit не ограничивает прямые HTTP-клиенты.
|
||||
- **Threat model:** account confidentiality / CPU availability → неаутентифицированный клиент с доступом к public auth → login/register → отсутствует доказанный admission budget → подбор credentials/накопление bcrypt work → existing generic errors, expensive hash, внешний proxy возможен → проверить perimeter и ограничить при необходимости.
|
||||
- **Impact / actual cost:** условный brute-force/resource-exhaustion risk. Частота попыток, доступность сервера, CPU budget и реальные controls **not measured / UNVERIFIED**; account compromise не заявляется.
|
||||
- **Why now:** credentials открывают доступ к сохранённым финансовым данным, а register/login намеренно публичны.
|
||||
- **Minimum change:** сначала прочитать реальную внешнюю proxy policy и путь обхода. Если защиты нет, задать ограниченный budget auth requests на существующей boundary, отдельно рассмотрев распределённый подбор одного account. Не добавлять lockout, CAPTCHA, Redis или новый auth subsystem без этой проверки.
|
||||
- **Guarantees at risk:** account lockout может дать attacker-controlled DoS; IP policy зависит от доверенной proxy chain. Generic login errors и dummy hashing сохраняются; logging попыток не должен содержать credentials/email values.
|
||||
- **Counterarguments / trade-offs:** root Compose публикует frontend на loopback, внешний reverse proxy обязателен для HTTPS и может уже фильтровать. Docs честно описывает app limitation. При закрытом доступе remediation priority может быть ниже; нет оснований для P0/P1 exploitation claim.
|
||||
- **Verification or rollback:** самый дешёвый шаг — static inspect live proxy configuration/правильность client IP без attack/load test; затем isolated auth boundary test rejection budget, отсутствие bcrypt/upstream work после rejection и восстановление доступа. Не проводить production brute force.
|
||||
- **Related finding IDs:** RISK-007.
|
||||
|
||||
## 5. Protected Invariants Registry
|
||||
|
||||
`Safe-to-remove? = No` означает отсутствие доказанной равносильной замены, а не необходимость сохранить каждую нынешнюю строку реализации.
|
||||
|
||||
| Invariant | Mechanism | Evidence | Safe-to-remove? |
|
||||
| --- | --- | --- | --- |
|
||||
| Client не выбирает identity; nested resource чужого User недоступен | Verified user.sub + scoped card/operation/category/account queries | `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts:94–166`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:62–72`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:100–197`; `backend/src/integrations/tbank/tbank-access.service.ts:262–288` | No |
|
||||
| Password hash и token не являются публичной User моделью | bcrypt и явный toUser projection | `backend/src/modules/auth/application/auth.service.ts:48–52,64–78,142–144`; `backend/src/modules/auth/presentation/auth.controller.ts:113–119` | No |
|
||||
| Один current refresh имеет одного победителя; loser не отзывает победителя | SHA-256 hash высокоэнтропийного token + conditional updateMany + unique jti | `backend/src/modules/auth/application/auth.service.ts:99–113,166–205`; `backend/test/app.e2e-spec.ts:161–214` | No |
|
||||
| Refresh cookie закрыта JS и ограничена auth boundary | HttpOnly, SameSite=Lax, Secure вне development, host-only/path | `backend/src/modules/auth/presentation/auth.controller.ts:125–137` | No |
|
||||
| Auth/session switch не публикует старый response/cache в новой identity | In-memory token, generation checks, clear/reset Query cache, serialised cookie writes | `frontend/src/features/auth/model/auth.vm.ts:41–115,121–151`; `frontend/src/shared/api/client.ts:53–119` | No |
|
||||
| Broker token зашифрован с integrity protection | AES-256-GCM с random nonce и authenticated tag; validated key | `backend/src/integrations/tbank/tbank.crypto.ts:14–49`; `backend/src/config/env.validation.ts:75–83` | No |
|
||||
| Failed token replacement не портит прежнюю connection; старые writes не проходят | Validation до DB transaction; fresh revision/cascade + attempt fences | `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:31–88`; `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts:121–147`; `backend/prisma/schema.prisma:25–71,232–262` | No |
|
||||
| Lost create response не даёт второй banking effect, changed intent не переиспользует ID | UUID primary key + normalized createFingerprint после ownership check | `backend/src/modules/banking/bank-cards/application/bank-cards.service.ts:74–95`; `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:214–240`; `backend/src/modules/banking/bank-cards/application/bank-card-input.ts:74–81` | No |
|
||||
| Deleted operation не воскресает от delayed create replay | Tombstone + same-ID replay → 410 | `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:221–230,286–330` | No |
|
||||
| Competing edit/refund сохраняет version и refund ceiling | CAS source/version, refund sum и write transaction/retry | `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:152–209,251–330`; `backend/src/modules/banking/bank-cards/infrastructure/bank-cards.repository.ts:35–59`; `backend/test/bank-cards.e2e-spec.ts:462–514` | No |
|
||||
| Деньги не теряют точность и не суммируются между валютами | BigInt decimal и RUB kopecks bounds; grouped currency totals | `backend/src/common/decimal.ts:19–83`; `backend/src/modules/banking/bank-cards/domain/rub-money.ts:12–38`; `backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts:109–138` | No |
|
||||
| Cursor сохраняет owner/filter/data identity | HMAC verification + owner/revision/filter binding | `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:347–388`; `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:128–148` | No |
|
||||
| Частичная external page не заменяет полный operation dataset | Stage marker, continuation checks, duplicate conflict detection, atomic rows+coverage+revision | `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:602–699,712–881` | No |
|
||||
| Forecast publication соответствует версиям обязательных dependencies | Connection write fence + portfolio/source revision/timestamp checks | `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:35–49,80–174` | No; исправление RISK-003 не удаляет fence |
|
||||
| Shared public sources не содержат personal holdings/credentials | Public projection, initiating/observing attempt authorization | `backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts:646–677`; `backend/src/modules/investments/instruments/infrastructure/catalog-discovery.repository.ts:154–180,252–277` | No |
|
||||
| Upstream overload и secret errors имеют bounded handling | TLS, queue bound, deadline, два RPC attempts, безопасные context/error codes | `backend/src/integrations/tbank/tbank.client.ts:49–54,214–218,504–586`; `backend/src/integrations/moex/moex.client.ts:164–198`; `backend/src/integrations/ncr/ncr-ratings.client.ts:36–57` | No |
|
||||
| Raw secrets/body/SQL/error stack не выдаются в HTTP completion/envelope | Problem filter + allowlisted completion/context | `backend/src/common/filters/http-exception.filter.ts:19–47`; `backend/src/common/logging/logging.module.ts:15–54`; `backend/src/common/logging/structured-logger.service.ts:7–25,43–60` | No; текущие messages дополнительно проверяются семантически |
|
||||
| Backup/restore не создаёт смесь БД и ключа/новых данных | Stop writers, full data-directory backup, отдельный key, explicit isolated restore | `docs/architecture/deployment.md:137–176` | No; runtime execution UNVERIFIED |
|
||||
|
||||
**Важные контрсценарии:** убрать fingerprint → retry с тем же UUID, но другой суммой больше не отличим от исходного intent; убрать tombstone → delayed POST сможет создать удалённую операцию; убрать CAS → stale PATCH или competing refund теряет установленную границу; убрать revision/attempt fence → ответ до replacement может опубликоваться после него. Эти механизмы не являются ритуальными guards.
|
||||
|
||||
### Never remove without replacement
|
||||
|
||||
- Server-side DTO validation, explicit identity/ownership и проверки всех nested references.
|
||||
- Bcrypt byte ceiling, safe public User projection, refresh hash CAS, token type/algorithm/expiry и cookie flags.
|
||||
- AES-GCM integrity/key validation, generation rotation, conditional late-publication fences.
|
||||
- Banking createFingerprint, client UUID, version CAS, tombstones, refund serialization и transaction rollback.
|
||||
- Exact Money representation и currency separation.
|
||||
- Staging completeness/continuation/duplicate guards и атомарное обновление rows/coverage/revisions.
|
||||
- Forecast dependency checks и сохранение последнего полного результата при partial failure.
|
||||
- TLS, queue bounds, timeout/retry limits, safe error mapping и log projections.
|
||||
- Durable attempt identity и восстановление после restart; RISK-001 требует усилить lifecycle, а не удалить его.
|
||||
|
||||
## 6. Failure Recovery Map
|
||||
|
||||
| Failure | Текущий механизм / observable state | Recovery и предел | Evidence / decision |
|
||||
| --- | --- | --- | --- |
|
||||
| Unknown email / wrong password | Generic INVALID_CREDENTIALS, dummy bcrypt для unknown user | Новый login; admission budget вне app не подтверждён | `auth.service.ts:64–78`; RISK-008 |
|
||||
| Concurrent / superseded refresh | Один hash CAS winner, loser 401 без Set-Cookie | Winner usable; loser допускает re-login; нет cross-tab lock/history | `auth.service.ts:99–113,183–190`; `auth.controller.ts:81–90`; KEEP |
|
||||
| Refresh/logout storage failure | 500 через safe filter, cookie не перезаписывается неуспехом | Возможен retry; frontend очищает локальную identity при failed refresh/logout | `backend/test/app.e2e-spec.ts:102–159`; `frontend/src/features/auth/model/auth.vm.ts:81–115,135–141`; tests не запускались |
|
||||
| Invalid replacement broker token | Validation вне transaction, InvalidToken translator | Старые connection/private rows сохранены | `tbank-connection.service.ts:31–72`; KEEP |
|
||||
| Upstream transient / queue full | Два bounded RPC attempts; queue overflow сразу 503 | Следующая штатная попытка, без вложенного service retry | `tbank.client.ts:504–568`; KEEP |
|
||||
| Lost banking POST response | Same UUID + same fingerprint → current DTO | Safe replay; changed intent 409; deleted replay 410 | `bank-operations.service.ts:214–240`; KEEP |
|
||||
| Competing banking edit / refund | CAS/transaction конфликт откатывает записи | Перечитать version и повторить осознанный edit | `bank-operations.service.ts:152–209,251–330`; KEEP |
|
||||
| Partial operations pagination | Staging не публикуется без complete marker | Old shown scope сохранён; re-reservation меняет attempt и очищает stage | `brokerage-operations.repository.ts:409–436,625–639,724–734`; KEEP |
|
||||
| Calendar/provider parse failure | Safe unavailable, старый full source/forecast сохраняется | Следующая authorized refresh; stable policy может не освежить сохранённый calendar | `catalog-enrichment.service.ts:524–578`; RISK-004 |
|
||||
| Child persistence failure / hourly settle failure | In-memory worker завершён, durable running может остаться | Automatic live-process recovery не гарантирован; restart помогает, но не заменяет нормальный recovery | RISK-001 |
|
||||
| Orchestration exception до completion | Общий catch с прежним списком failures | Может falsely succeeded; требуется явный unsuccessful aggregate | RISK-002 |
|
||||
| Process restart | Другой processRunId отменяет hourly running, срок определяется началом прежней попытки | Не более одного актуального следующего запуска; multi-process lease отсутствует намеренно | `tbank-sync.repository.ts:174–220`; single process — root `compose.yaml:32–38` |
|
||||
| Old forecast / changed request window | Exact scope либо первый overlap | Новый full candidate может быть отвергнут; non-overlap без candidate даёт null | RISK-003 |
|
||||
| Cached portfolio + read failure | Cached payload сохраняется | Status/retry на loaded page теряются | RISK-005 |
|
||||
| Внешне отозванный token до background detection | Online path даёт safe 500 | Frontend повторяет transient GET вместо нужного recovery | RISK-006 |
|
||||
| SQLite/disk/key loss | Docs требует согласованный backup и isolated restore | Локальный backup не защищает от потери диска; live/off-host restore и сохранность ключа UNVERIFIED | `docs/architecture/deployment.md:137–176` |
|
||||
|
||||
Сокращённые имена source files в этой таблице относятся к полным evidence в findings и registry; это карта механизмов, не новые findings.
|
||||
|
||||
## 7. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Объект | Основание |
|
||||
| --- | --- | --- |
|
||||
| DELETE | Подтверждённых безопасных удалений protective mechanisms нет | No replacement proof; общий dead-code cleanup — другой audit |
|
||||
| SIMPLIFY | Подтверждённых обязательных complexity changes нет | Новый workflow/retry/lease framework не решает найденные локальные дефекты |
|
||||
| KEEP | Ownership, refresh CAS/cookie boundary, AES-GCM | Защищают identity/credentials, не заменяются UI validation |
|
||||
| KEEP | Banking UUID + fingerprint + version CAS + tombstones + transactions | Разные сценарии: replay, changed intent, stale edit, delayed resurrection, refund concurrency |
|
||||
| KEEP | Connection/attempt revisions, stage completeness, forecast dependency checks | Защита late publication, partial reconciliation и исходных финансовых assumptions |
|
||||
| KEEP | Exact decimal/BigInt/currency grouping | Защита monetary correctness |
|
||||
| KEEP | Bounded RPC retry/queue/TLS, safe Problem/log projections | Необходимые integration/error boundaries |
|
||||
| KEEP | Один native scheduler timer без inter-process lease | Минимально достаточно при подтверждённом single-process deployment contract; перед scaling нужна замена |
|
||||
| FIX | RISK-001–007 | Восстановление, достоверность результатов и согласованность runtime boundaries |
|
||||
| INVESTIGATE | RISK-008: external auth admission policy | Приложение не ограничивает auth; production perimeter не проверен |
|
||||
| INVESTIGATE | Off-host backup + original encryption key + live restore | Инструкции есть, выполнение/сохранность копий не доказаны; не объявляется code vulnerability |
|
||||
|
||||
## 8. Cost & benefit
|
||||
|
||||
| Action | Класс ожидаемой пользы / стоимости | Фактическое измерение | Обязательная граница регрессии |
|
||||
| --- | --- | --- | --- |
|
||||
| RISK-001/002 | Reliability, диагностируемость, меньше бесконечного polling/manual restart; локальные дополнительные branches | not measured | Late attempt fencing, partial success, DB rollback, восстановление без data deletion |
|
||||
| RISK-003/004 | Financial correctness/freshness; calendars потребуют дополнительных RPC | not measured | Старый full forecast при failure, passive GET, shared-source isolation, rate budget |
|
||||
| RISK-005 | User trust/recovery; reuse existing status component | not measured | Cached payload не стирается; auth/access change его скрывает |
|
||||
| RISK-006 | Runtime traffic и корректное recovery постоянной ошибки | not measured; максимум три frontend GET attempts — только source bound | Transient retry остаётся bounded; новая connection не помечается старым failure |
|
||||
| RISK-007 | Меньше rejected auth input и cognitive load несогласованной policy | not measured | Bcrypt 72-byte ceiling и неизменность password bytes |
|
||||
| RISK-008 verification | Подтвердить существующую защиту прежде, чем добавить новую | not measured | Trusted client IP, отсутствие attacker-induced account lockout |
|
||||
| KEEP registry | Избежать data-loss/security regression от cleanup | not measured | Сохранение всех перечисленных invariants |
|
||||
|
||||
Экономия CI minutes, agent tokens, процентов runtime, строк кода/dependencies не измерялась и не заявляется. Подключение нового Redis, distributed scheduler или security framework не обосновано текущей проверенной областью.
|
||||
|
||||
## 9. Top actions
|
||||
|
||||
### Quick wins — подтверждённые локальные дефекты
|
||||
|
||||
1. **RISK-005 / S:** передать существующий portfolio metadata prop и показать read failure/retry, сохраняя snapshot.
|
||||
2. **RISK-006 / S:** подключить existing invalid-token translation к online paths, проверить status/code и отсутствие frontend повторов постоянной ошибки.
|
||||
3. **RISK-002 / S:** общий exception обхода завершать явным unsuccessful outcome; не выдавать `succeeded` до обязательного completion.
|
||||
4. **RISK-004 / S:** targeted hourly refresh только payout calendars через существующий механизм force/TTL.
|
||||
5. **RISK-007 / S:** выровнять runtime UTF-8 bounds по выбранной password policy.
|
||||
|
||||
### Требуют test proof / уточнения семантики
|
||||
|
||||
6. **RISK-001 / M — главный reliability priority:** bounded wait и recovery abandoned/settlement work в живом процессе; isolated SQLite failure injection и поздний ответ старого attempt.
|
||||
7. **RISK-003 / M:** закрепить разделение hourly full publication и фильтрующего forecast GET, затем исправить selection/persistence. ADR нужен только при изменении самой behavioral policy, а не ради локальной правки guard.
|
||||
|
||||
RISK-008 не превращён в подтверждённое внедрение rate limiter: сначала дешёвая verification внешнего proxy в следующем разделе. Масштабный рефакторинг, новое хранилище и миграции не входят в эти действия.
|
||||
|
||||
## 10. Cross-audit signals
|
||||
|
||||
- **08 — Test quality:** отсутствие прямых hourly `tick/reserveHourly/settleHourly` assertions и старые local-read race сценарии; `backend/test/upcoming-events.e2e-spec.ts:140–147`, `backend/test/tbank-local-reads.e2e-spec.ts:489–515`. Не дублировать общий audit test quality; relevant regression cases уже перечислены выше.
|
||||
- **07 — API/data-flow efficiency:** income read ещё инициирует membership/request planning; `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:408–435`. Это другой owner refresh, а не отдельная security находка данного аудита.
|
||||
- **15 — SDD/docs:** существующий events DTO не содержит исходное forecast window, хотя spec его требует; `backend/src/modules/investments/upcoming-events/upcoming-events.dto.ts:68–79`, `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:12,86–87`. Требуется согласовать contract, не добавить произвольное поле в рамках этого read-only аудита.
|
||||
- **06/15 — Backend policy/spec coherence:** eligibility длительного обхода разрешается по elapsed hour вместо пропущенного term; `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:190–201`, `specs/openspec/specs/hourly-investment-sync/spec.md:19–25`. Не трактуется как overlap эксплуатация: running guard существует.
|
||||
- **12 — Configuration:** off-host backup, permissions ключей и внешние HTTPS/rate policies принадлежат реальному deployment; `docs/architecture/deployment.md:123–143`. Не создавать дублирующий конфигурационный аудит без этих inputs.
|
||||
|
||||
## 11. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Текущее основание | Самый дешёвый следующий proof |
|
||||
| --- | --- | --- |
|
||||
| Ограничивает ли live external HTTPS proxy auth и можно ли обойти его? | RISK-008; frontend Nginx без limit, root port на loopback | Пассивно посмотреть реальную proxy policy и network exposure. Если контролируется — KEEP существующую защиту, не внедрять второй limiter |
|
||||
| Какой deadline допустим для большого hourly portfolio и как fence отменяет старую работу? | RISK-001; нет верхней границы aggregate waiter | Controlled isolated failure case сначала; затем representative длительность без live accounts. Deadline не должен разрешать stale publication |
|
||||
| Чем отделяются новая полная forecast publication и простое фильтрующее чтение? | RISK-003 и существующий narrow-window test | Утвердить ожидаемые outcomes трёх случаев: new full candidate, failed refresh, filter-only read. Один isolated SQLite case на каждую границу |
|
||||
| Можно ли доказать auth recovery при зависшем, а не rejected transport? | `frontend/src/shared/api/client.ts:127–131` обходит GET deadline для auth; `frontend/src/features/auth/model/auth.vm.ts:85–118,135–140` ожидает serialised запрос | MSW deferred refresh без ответа + controlled timer. Source показывает отсутствие app deadline; необходимость и допустимый срок требуют принятого bootstrap/recovery contract. До этого не добавлять speculative timeout во все mutations |
|
||||
| Какая auth atomicity нужна при failure создания первой Session после User commit? | `backend/src/modules/auth/application/auth.service.ts:48–61,152–164`: отдельные writes | Isolated failure в session.create после user.create; определить требуемый rollback или явный login recovery. Account сохраняется и login возможен, поэтому не заявлена потеря credentials/critical exploit |
|
||||
| Должны ли concurrent logout/refresh возвращать строго один terminal session outcome? | `backend/src/modules/auth/application/auth.service.ts:95–113,127–138,183–190`: refresh CAS только по hash, logout проверяет hash до отдельного update | Controlled two-request barrier на isolated DB; согласовать семантику до расширения CAS. Bounded revocation принята в `docs/adr/006-auth-session-policy.md:11–15`; access до 15 минут после logout явно допускается в `docs/domains/authentication/overview.md:89–95`, сам по себе это не auth bypass finding |
|
||||
| Сохранены ли independent backup и original encryption key, выполнялся ли restore? | `docs/architecture/deployment.md:137–176`; AES-GCM не читает данные с новым ключом | Проверить только наличие процедуры/метаданные подтверждённого restore, не читать secrets/PII. При отсутствии proof — изолированный restore существующими средствами в отдельной разрешённой задаче |
|
||||
| Missing MOEX block действительно означает отсутствие событий или повреждённый контракт? | `backend/src/integrations/moex/moex.client.ts:182–198` vs spec `brokerage-account-upcoming-events:82–84` | Согласовать provider contract на обезличенном response fixture. До этого KEEP malformed-row/cursor guards, не превращать unknown в подтверждённый пустой календарь |
|
||||
|
||||
### Завершение и ограничения доказательств
|
||||
|
||||
Проведена смысловая сверка findings с source evidence, consumer paths, контрсценариями и защищаемыми invariants. У каждого существенного defect указан runnable verification scenario для последующей исправляющей задачи. В этом аудите тесты и exploitation не проводились, поэтому ни один defect не назван runtime-воспроизведённым.
|
||||
|
||||
Финальная проверка: root/backend/frontend/specs/docs остаются с пустым `git status --short`; `git diff --check` не сообщает ошибок. Новый ignored отчёт просмотрен отдельно и проверен `git diff --no-index --check /dev/null audit-results/13-reliability-and-security-report.md`: обычный Git diff его не проверяет. `git check-ignore` подтверждает путь вне root allowlist. Boy Scout review отчёта: evidence не опирается на предыдущий аудит, severity не выведена из наличия guards, лишние удаления/метрики/абстракции не предложены. Исходники, specs, docs, тесты, конфигурации и history не изменены.
|
||||
|
||||
## 12. Consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| RISK-001 | FIX | P1 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts; src/modules/investments/instruments/application/catalog-enrichment.service.ts; src/modules/investments/operations/application/brokerage-operations-sync.service.ts | Persistence failure может оставить running и заблокировать дальнейшие обходы живого процесса | M | RISK-002,RISK-004 |
|
||||
| RISK-002 | FIX | P2 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; src/modules/investments/tbank-sync/tbank-sync.repository.ts | Неисполненный из-за общего exception обход может считаться succeeded | S | RISK-001,RISK-005 |
|
||||
| RISK-003 | FIX | P2 | High | backend | src/modules/investments/portfolio/infrastructure/forecast.repository.ts; src/modules/investments/portfolio/application/forecast.service.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Старый overlapping forecast блокирует новый полный результат изменившегося окна | M | RISK-004,RISK-005 |
|
||||
| RISK-004 | FIX | P2 | High | backend | src/modules/investments/instruments/application/catalog-enrichment.service.ts; src/modules/investments/upcoming-events/upcoming-events.service.ts | Stable calendar policy пропускает новые/изменённые выплаты в hourly refresh | S | RISK-001,RISK-003 |
|
||||
| RISK-005 | FIX | P2 | High | frontend | src/pages/account/ui/AccountPage/AccountPage.tsx; src/pages/account/ui/AccountPage/AccountPage.vm.ts; src/pages/account/ui/AccountPage/AccountPage.test.tsx | Cached financial sums показаны без уже подготовленной freshness/error metadata | S | RISK-002,RISK-003,RISK-004 |
|
||||
| RISK-006 | FIX | P2 | High | backend,frontend | backend/src/modules/investments/instruments/application/catalog-discovery.service.ts; backend/src/modules/investments/instruments/application/catalog-enrichment.service.ts; frontend/src/shared/api/client.ts | Постоянный invalid token превращается в 500 и повторные HTTP reads | S | RISK-005 |
|
||||
| RISK-007 | FIX | P2 | High | backend,frontend | backend/src/modules/auth/presentation/auth.dto.ts; frontend/src/features/auth/model/auth.schema.ts | Non-ASCII password проходит frontend byte minimum и отвергается backend character minimum | S | RISK-008 |
|
||||
| RISK-008 | INVESTIGATE | P2 | Medium | backend,frontend | backend/src/modules/auth/presentation/auth.controller.ts; backend/src/modules/auth/application/auth.service.ts; frontend/docker/nginx.conf | Public auth не ограничен в app/provided proxy; live perimeter protection UNVERIFIED | S | RISK-007 |
|
||||
@@ -0,0 +1,342 @@
|
||||
# Аудит 14 — AI Agent Workflow Efficiency
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Дата:** 2026-10-11.
|
||||
- **Основание:** `audit-prompts/14-ai-agent-workflow-efficiency.md`.
|
||||
- **Результат:** этот файл; имя выбрано по прямому запросу пользователя, вместо имени с суффиксом `-report`, указанного в шаблоне.
|
||||
- **Режим:** статический read-only аудит четырех репозиториев и анализ предоставленного локального `session.json`. Единственная запись — итоговый отчет вне четырех репозиториев.
|
||||
- **Workspace/cwd:** `/Users/ksv741/Project/vibe/WealthLedger`; ниже пути относительно него. `~/…` обозначает локальную глобальную конфигурацию, которая не входит в Git проекта.
|
||||
|
||||
| Repo | Проверенный путь | Точный HEAD | Начальный status |
|
||||
| --- | --- | --- | --- |
|
||||
| root | `/Users/ksv741/Project/vibe/WealthLedger` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | clean |
|
||||
| frontend | `/Users/ksv741/Project/vibe/WealthLedger/frontend` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | clean |
|
||||
| backend | `/Users/ksv741/Project/vibe/WealthLedger/backend` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | clean |
|
||||
| specs | `/Users/ksv741/Project/vibe/WealthLedger/specs` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | clean |
|
||||
| docs | `/Users/ksv741/Project/vibe/WealthLedger/docs` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | clean |
|
||||
|
||||
**Skills.** В начале через Skill загружен установленный `ponytail-audit`. Применены его критерии удаления процессной сложности, поиска существующих средств и минимального изменения. Его краткий формат расширен обязательной структурой этого аудита; потенциальные строки/зависимости к удалению не выдумываются. Остальные skill-файлы читались как предмет исследования, без запуска их workflow.
|
||||
|
||||
**Что выполнялось:** Read/Glob/Grep; `git status --short`; `git rev-parse --show-toplevel HEAD`; `git config --show-origin --get core.hooksPath`; `command -v opencode`; `ls` для проверки каталога результата и типа каталогов skills; read-only `jq`-агрегации существующего JSON. При отсутствии `core.hooksPath` запрос Git возвращает отсутствие значения, а не поломку репозитория. Не найденный `specs/.githooks/pre-commit` проверен перечислением каталога: там только `commit-msg`.
|
||||
|
||||
**Не выполнялось:** install, Node-команды, tests, lint, build, verify, codegen, OpenSpec CLI, hooks, graph generation, обращения к MCP/внешним сервисам, commit/PR, изменения конфигураций или исходников. Команды тестирования внутри исторического trace — наблюдаемые записи прежнего сеанса, не запуски этого аудита.
|
||||
|
||||
**Ограничения:** глобальные настройки, локальные `.agents/skills` и `session.json` не versioned этим root allowlist; указанный root SHA не фиксирует их содержание. Startup/debug logs текущего harness не исследованы. Не открывались auth-файлы, БД истории, чужие глобальные sessions или банковские данные. Значения credentials из конфигураций не приводятся. `technical-audit-2026-10-11.md` использован только как прежний реестр: отдельного доказательства workflow-проблем он не дает. Отчет локальный и исключен root allowlist (`.gitignore:1–7`); добавление в tracking не выполняется.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт:** действующие repo routers уже обеспечивают selective context и самостоятельность checkout. Нет обязательного правила читать все четыре репозитория для каждого UI/backend изменения. Это следует сохранить, а не заменять новым общим руководством.
|
||||
2. **Подтвержденный механизм затрат:** для любого code/dependency change оба engineering-файла требуют `npm ci` и полный `verify`. В SDD task-dispatch это становится требованием каждого исполнителя. Два завершенных исполнителя в trace сообщают о full verify; фактические subprocess traces и длительность этих gates отсутствуют. Размер экономии **not measured**.
|
||||
3. **Подтвержденные конфликты инструкций:** локальный risk-based testing конкурирует с универсальным TDD; scoped reviewer — с требованием «fresh verification в этом сообщении»; project approval flow — с generic autonomous rulings. Дополнительные отрицания в dispatch prompt сейчас компенсируют часть конфликтов.
|
||||
4. **Наблюдаемый локальный сбой:** `nvm use --silent` без bootstrap завершился `127`; следующий вызов с загрузкой `nvm.sh` выполнил целевой тест. Это дешевый устранимый retry, а не свидетельство медленных тестов.
|
||||
5. **Факт о выборке:** родительский SDD export содержит 53 tool parts и записанные token counts. Повторных Read одного пути нет. Две отмены task и повторные dispatch видны; причины отмен и объем потерянной работы **UNVERIFIED**. Обвинять модель или считать все время сеанса overhead нельзя.
|
||||
6. **Непроверенная гипотеза:** глобальный каталог MCP/skills и широкий graphify trigger увеличивают нерелевантный контекст. Экспозиция инструментов подтверждена, их вклад в tokens/latency — нет. До измерения нужны селективные triggers, а не массовое удаление полезных инструментов.
|
||||
7. **KEEP:** approval, explicit store, публичные API artifacts, финансовые/DB/auth regression boundaries и независимый semantic review. В исторической второй задаче review действительно выявил нарушение exact-UID требования после зеленых тестов; это контрпример предложению удалить review целиком.
|
||||
|
||||
**Итог:** пять подтвержденных instruction/config findings и одна исследовательская гипотеза. P0/P1 не установлены. Основной путь улучшения — согласовать существующие правила и единицу verification, без новой инфраструктуры.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### 3.1. Что реально изучено
|
||||
|
||||
| Область | Проверенные объекты | Граница выводов |
|
||||
| --- | --- | --- |
|
||||
| Repo entry points | Root и четыре локальных `AGENTS.md`; frontend/backend engineering/testing; frontend architecture/UI/data; backend HTTP API; specs README/AI_INDEX; docs CONTRIBUTING | Правила прочитаны; их соблюдение во всех прежних sessions не доказано |
|
||||
| Verification consumers | frontend/backend/specs/docs `package.json`; frontend `.husky/pre-commit`, `commit-msg`, установленный Husky launcher; specs `.githooks/commit-msg`; Git hooksPath | Прослежены точки запуска, без аудита семантики custom scripts или CI topology |
|
||||
| SDD | `specs/openspec/config.yaml`, v2 `schema.yaml`, установленный стандартный `openspec-apply-change` | Approval/dependencies/context loading; весь массив canonical specs и archives не читался |
|
||||
| Generic workflows | `subagent-driven-development` и implementer/reviewer templates; TDD; verification-before-completion | Проверены реальные противоречия и capability mismatch, без исполнения workflow |
|
||||
| Global OpenCode | `~/.config/opencode/opencode.jsonc`, `plugins/rtk.ts`, установленный Ponytail plugin | Регистрация/триггеры; tool transport startup latency не измерена |
|
||||
| Другие harness | `~/.claude/CLAUDE.md`, RTK.md, settings; пустой `~/.codex/AGENTS.md`, релевантные config declarations и hooks.json | Наличие и регистрация не приравниваются к исполнению в OpenCode |
|
||||
| UI conventions | AccountIncomePage container и BankCardForm consumer; FSD skill sections о selective references/extraction | Проверка обвинения в обязательных пустых VM/wrappers; не frontend architecture audit |
|
||||
| Runtime sample | Workspace `session.json`, parent messages/tool states/token counters | Один незавершенный SDD-сеанс; child tool traces отсутствуют |
|
||||
|
||||
Frontend — React/TypeScript/Vite с Mantine, Vitest и локальными packages; backend — NestJS/Prisma/SQLite с Jest. Стек проверен по manifests и локальным инструкциям, не принят из старого отчета.
|
||||
|
||||
На glob по root нельзя полагаться для nested repos: root исключает их из tracking. Поэтому entry points и поиск локальных AGENTS выполнялись с явными repo paths. В охваченном поиске frontend/backend/specs/docs найдено по одному `AGENTS.md`; repo listings не показывают локальных `.opencode`/`.claude` configs. Это не доказательство отсутствия всех возможных environment overrides.
|
||||
|
||||
### 3.2. Метрики и метод
|
||||
|
||||
Источник — JSON export; обход `.messages[].parts[]`, только `type == "tool"`. Считаются вызовы родителя, включая отмененные и незавершенный. Tokens суммированы **один раз** из assistant `.info.tokens`, без повторного учета `step-finish.tokens`; сумма сверена с `.info.tokens` сеанса. Размеры outputs — `utf8bytelength(.state.output)`, без дубликатов из metadata. Байты не конвертируются в tokens.
|
||||
|
||||
| Метрика | Значение | Охват/интерпретация |
|
||||
| --- | --- | --- |
|
||||
| Сообщения | 42: 38 assistant, 4 user | Только parent export |
|
||||
| Tool parts | 53 | bash 19; read 12; task 9; skill 4; write 3; grep 2; edit 2; todowrite 2 |
|
||||
| Повторное чтение пути | 0 повторов среди 12 Read | Не включает shell reads и инструменты children |
|
||||
| До первого implementer dispatch | 34 tool parts | Включает approvals, необходимые contextFiles и два explore; не означает 34 лишних вызова |
|
||||
| Записанные input tokens | 449369 | Provider/harness counters, не независимый tokenizer |
|
||||
| Записанные cache read tokens | 2601600 | Сумма по turns, не число уникальных tokens контекста |
|
||||
| Записанные output / reasoning tokens | 13682 / 17208 | Отдельные поля export; не суммируются в придуманную billing-модель |
|
||||
| Tool output bytes | 242449 | `.state.output` родителя |
|
||||
| Из них четыре Skill outputs | 58388 | apply, subagent workflow, TDD, verification; без injected system prompt |
|
||||
| Два explore outputs | 25197 | Backend/frontend mapping; полезность каждой строки не измерена |
|
||||
| Самый большой tool output | 33577 bytes | Загрузка subagent-driven-development |
|
||||
| Dispatch prompt bytes | 26357 | Девять task inputs, включая отмены и resume |
|
||||
| Отмены / незавершенный task | 2 / 1 | Отмены сопровождаются MessageAbortedError; последний fix имеет status running |
|
||||
| Bash с nonzero exit | 1 | `nvm` отсутствует; exit 127 |
|
||||
| Интервал export | 2026-10-10 18:59:47–20:17:09 UTC; 4642.105 s | `updated-created`; включает ожидание пользователя и children, не active processing time |
|
||||
|
||||
Evidence: `session.json:1–34` — metadata; `:2970–2994`, `:3280–3300`, `:3961–3979`, `:4070–4089`, `:4392–4410`, `:4511–4530` — task states; `:3420–3434` — failed shell. Числа получены read-only `jq` с фильтрацией/group_by и суммированием, а не оценкой по размерам файлов.
|
||||
|
||||
**Стоимость исполнения:** `cost: 0` в export не доказывает бесплатную работу. Полные costs/tokens children, стоимость tool catalog/system instructions, чистый verification runtime и контрфактическая экономия **NOT MEASURED**. Отдельно выделенный **runtime agent overhead NOT MEASURED**, хотя session usage counters доступны.
|
||||
|
||||
Репрезентативность ограничена: один явно запрошенный SDD feature с делегацией, TDD и review. Нет runtime samples small UI fix или обычного backend contract change; их последовательности ниже — рекомендации, не измерения. Точные HEAD четырех repo в export не установлены; его snapshot hashes не приравниваются к repo commits. Историческое исполнение не приписывается текущим HEAD автоматически. Reports/briefs из прежнего `.superpowers` workspace изучались только по встроенному содержимому export, без подтверждения наличия этих файлов сейчас.
|
||||
|
||||
## 4. Instruction Precedence Map
|
||||
|
||||
| Уровень/источник | Реальное включение | Правило разрешения |
|
||||
| --- | --- | --- |
|
||||
| System/developer текущего harness | Уже передан в текущем сеансе: инструменты, cwd, инструкции OpenCode, правила выбора skills и запрет незапрошенной делегации | Имеет более высокий приоритет, чем project/skill text; каталог навыков не означает загрузку всех тел |
|
||||
| Прямой user request / audit protocol | Пользователь назвал prompt и точный файл результата; protocol прочитан | Read-only ограничения определяют этот аудит; создание feature/SDD артефактов не активировано |
|
||||
| Global `~/.claude/CLAUDE.md` | Прямо объявлен harness как loaded instruction source в текущем сеансе; внутри ссылка `@RTK.md` | Само включение подтверждено; детали loader fallback не установлены. RTK.md дополнительно прочитан вручную, его автоматическое раскрытие не предполагается |
|
||||
| Root `AGENTS.md` | Объявлен loaded source; cwd находится в root | Root правила относятся к root; локальные application conventions не наследуют root conventions (`AGENTS.md:9–13`) |
|
||||
| Repo-local AGENTS | В этом аудите явно прочитаны; при чтении файлов repo harness дополнительно показывает соответствующий reminder | Владелец реализации — affected repo. Повторный reminder не требует второго Read того же файла |
|
||||
| Topic conventions | Через локальные task/path triggers, не все сразу | Объединить применимые темы, убрать дубликаты путей; engineering entry point обязателен в frontend/backend |
|
||||
| SDD context/config/schema | Только при explicit OpenSpec; CLI возвращает contextFiles, selected schema и tasks | Approval/contract — проектный semantic authority; structural readiness не является approval |
|
||||
| Loaded skills | Тело появляется после Skill invocation или целевого чтения | Уточняют способ выполнения, не отменяют user scope, repo safety или утвержденные contracts |
|
||||
| Ponytail plugin | Config registration + `experimental.chat.system.transform`; активный ruleset виден в текущем harness | Это injected text, не отдельный Skill call на каждом turn. Число последовательных копий в prompt не установлено |
|
||||
| Codex/Claude settings/hooks | Конфигурации существуют; нет runtime traces их исполнения в выбранном OpenCode sample | Не переносить их subprocess costs на OpenCode по факту наличия файлов |
|
||||
|
||||
Evidence: root `AGENTS.md:9–32`; frontend/backend `AGENTS.md:3–26`; specs `AGENTS.md:3–19`; `~/.claude/CLAUDE.md:1–5`; `specs/openspec/config.yaml:55–62`; v2 `schema.yaml:592–609`. Реальный механизм Ponytail: `~/.config/opencode/opencode.jsonc:6–9` → установленный package `.opencode/plugins/ponytail.mjs:51–82`.
|
||||
|
||||
Каталоги `.agents/skills`, `~/.config/opencode/skills`, `~/.codex/skills`, `~/.claude/skills/graphify` проверены как directories, не top-level symlinks. Это не исключает ссылок глубже. Происхождение global Claude fallback и discovery internals — **UNVERIFIED**; loaded-source declaration и видимый skill catalog достаточны для утверждения об экспозиции, но не для вывода о конкретном loader algorithm.
|
||||
|
||||
## 5. Agent Hook/Tool Trigger Matrix
|
||||
|
||||
| Событие | Агент/prompt | Hook/subprocess | Что установлено |
|
||||
| --- | --- | --- | --- |
|
||||
| Read / Grep / Glob | Router может активировать conventions/skills | В просмотренном OpenCode RTK plugin эти tools отфильтрованы | Нет найденного автоматического lint/test/build при чтении |
|
||||
| Edit / apply_patch | Изменение путей активирует rerouting; code change требует final gates по engineering | Просмотренные RTK/Ponytail hooks не запускают application checks на edit | Проверки выбирает агент; наличие других environment hooks не исключено |
|
||||
| Bash/shell | Команду выбирает агент из task verification/conventions | RTK проверяет binary при инициализации, затем вызывает `rtk rewrite <command>` до каждого bash/shell | Rewrite — дополнительный subprocess, не test runner; latency NOT MEASURED |
|
||||
| Каждый OpenCode system transform | Активен Ponytail mode | Plugin читает mode и добавляет ruleset к system output | Нет запуска lint/test/build; накопление старых копий не доказано |
|
||||
| `npm ci` frontend | Требуется engineering для code/dependency change | Manifest `prepare: husky` | Может настраивать локальные hooks; это не read-only проверка |
|
||||
| `npm run verify` frontend | Full final gate; может попасть в каждый worker brief | Package build, OpenAPI check, types, ESLint, lint-rule tests, Vitest coverage, Knip, два dependency graphs, app и Storybook builds | Триггер проверен; внутренние дубли/CI topology отнесены audit 11 |
|
||||
| `npm run verify` backend | Аналогичное local требование | Lint, lint-rule tests, два typecheck, Jest coverage, Prisma generate/Nest build | В sample два исполнителя сообщают о запуске; child command traces отсутствуют |
|
||||
| Git commit frontend | Агент обязан видеть результат hooks | Активный `.husky/_`; pre-commit → Gitleaks, lint-staged с ESLint `--fix`, `npm run deps`; commit-msg → commitlint | Depcruise запускает hook, не Read/Edit; lint-staged может изменить staged content |
|
||||
| Git commit specs | Commit metadata | Активный `.githooks`; только `commit-msg` → commitlint | Pre-commit/full validation hook не найден |
|
||||
| Git commit root/backend/docs | Правила подготовки commit | Явный `core.hooksPath` не задан в прочитанной конфигурации | Отсутствие custom hooks в `.git/hooks` не утверждается: их содержимое не проверялось |
|
||||
| Codex PreToolUse/PostToolUse и lifecycle | Не выбранный runtime sample | `~/.codex/hooks.json` регистрирует Caveman adapters, PreToolUse также shrink-hook; config содержит trusted hashes | Регистрация подтверждена; runtime вызовы и стоимость UNVERIFIED |
|
||||
| Claude lifecycle | Global plugins enabled | В `~/.claude/settings.json` собственный `hooks` block не найден | Plugin hooks отдельно не изучены; фраза RTK.md про transparent Claude rewrite не является доказательством его исполнения |
|
||||
| OpenSpec Apply | Store/status/apply context по явному запросу | CLI не является edit hook; после task checkbox стандартный skill требует refresh instructions | Не сокращать через угадывание путей/checkbox line; payload filtering не должно скрывать обязательный context |
|
||||
|
||||
Evidence: `~/.config/opencode/plugins/rtk.ts:10–36`; Ponytail plugin `:73–95`; `frontend/package.json:14–38`; `.husky/pre-commit:1–3`, `.husky/commit-msg:1`, `.husky/_/h:6–17`; `backend/package.json:12–38`; `specs/package.json:8–14`; `.githooks/commit-msg:1–2`; `~/.codex/hooks.json:23–68`; `~/.codex/config.toml:269–314`; `~/.claude/settings.json:1–33`; OpenSpec apply skill `:104–112`.
|
||||
|
||||
## 6. Findings
|
||||
|
||||
### AGENT-001 — Verification привязан к каждому code change/worker, включая переустановку
|
||||
|
||||
- **Decision = SIMPLIFY; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `frontend/.ai/conventions/engineering.md:58–66`; `backend/.ai/conventions/engineering.md:58–66`; `frontend/package.json:26–27`; `backend/package.json:34`; `session.json:2975–2993,4071–4089`.
|
||||
- **Claim:** единый финальный gate оправдан, но правило `npm ci` + full `verify` не различает восстановление environment, целевую task и готовность repo delivery.
|
||||
- **Current mechanism:** локальный router ведет в engineering; dispatch требует полного backend verify до отчета каждого исполнителя. Worker 1.1 и worker 1.2 сообщают full verify на последовательных состояниях одного checkout. `npm ci` предписан текстом, но его исполнение в этой выборке не видно.
|
||||
- **Impact / actual cost:** лишние install/full checks при уже готовом environment и дроблении feature. Full verify включает coverage/build, у frontend также Storybook. **Impact unmeasured**; две декларации запуска — не измерение двух реальных длительностей.
|
||||
- **Why now:** это прямой consumer локальных инструкций в доступном SDD-сеансе; одинаковое требование применяется и к небольшому UI diff.
|
||||
- **Minimum change:** в существующем разделе gates отделить install при отсутствующих/измененных dependencies, targeted iteration и full verify один раз на итоговом состоянии affected repo. Worker проверяет свою boundary; repo owner закрывает финальный gate. Explicit approved task Verification остается обязательной и не отменяется этой оптимизацией.
|
||||
- **Guarantees at risk:** общий type/build/import graph; fresh Prisma/package artifacts; money/auth/DB/contract regression checks. Полный финальный gate и risk checks сохранить, не заменять их одним changed-file lint.
|
||||
- **Counterarguments / trade-offs:** `npm ci` полезен для подозрительного или несовместимого environment; per-task full gate дает ранний сигнал интеграции. Поэтому исключение должно зависеть от готовности environment и типа diff, а не от желания ускориться. Markdown-only путь уже дешевый — исправлять его не требуется.
|
||||
- **Verification or rollback:** одна small UI задача и одна backend task с неизмененным lockfile; сравнить реальные install/check invocations, сохранив финальный full gate. При ошибке из stale environment восстановить clean install. До согласования локального правила действующее full verify требование сохраняется.
|
||||
- **Related finding IDs:** AGENT-002, AGENT-004.
|
||||
|
||||
### AGENT-002 — Несогласованные testing/fresh-evidence rules требуют лишней работы
|
||||
|
||||
- **Decision = SIMPLIFY; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `frontend/.ai/conventions/testing.md:13–24`; `backend/.ai/conventions/testing.md:13–20,34–42`; `.agents/skills/workflow/test-driven-development/SKILL.md:18–45,185–193,293–306`; `.agents/skills/workflow/verification-before-completion/SKILL.md:17–35,64–71,100–114`; `.agents/skills/workflow/subagent-driven-development/task-reviewer-prompt.md:73–92`; `session.json:3380–3428,3517–3533`.
|
||||
- **Claim:** project risk-based testing допускает declaration/thin wiring без нового unit test, а generic TDD требует test-first для любого production code и тест каждой новой функции. Отдельно правило fresh evidence «в этом сообщении» конфликтует с переиспользованием проверенного отчета reviewer.
|
||||
- **Current mechanism:** оба skills опубликованы в текущем catalog; в sample оба загружены. Reviewer template запрещает rerun без конкретного сомнения; после review coordinator объявляет independent check и повторно запускает целевой Jest. Начальный user prompt также прямо просил coordinator verification — повтор нельзя целиком приписать skill.
|
||||
- **Impact / actual cost:** один дополнительный успешный parent target-test вызов после worker/reviewer; перед ним одна failed shell попытка. Наличие бессмысленных новых tests из-за TDD в текущих исходниках не доказано. **Impact unmeasured** для общего потока.
|
||||
- **Why now:** правила не дают единого ответа, что считается task proof; агент повторно принимает решение вместо использования одного evidence contract.
|
||||
- **Minimum change:** применимость generic TDD ограничить observable behavior/regression; declaration/generated/low-risk cosmetic changes следуют local testing. Fresh evidence связывать с проверенной версией diff, command, exit и outcome, а не номером текущего сообщения. Reviewer проверяет diff и соответствие evidence; повторяет test при named doubt или изменении проверенного кода.
|
||||
- **Guarantees at risk:** обнаружение реальной регрессии, честный RED/GREEN для bug/новой логики, достоверность worker evidence. Не принимать один `DONE`; если отчет неполный или outcome/версия не совпадают, проверить целевую boundary заново.
|
||||
- **Counterarguments / trade-offs:** independent execution нужен при явном user требовании, подозрительном evidence или чувствительной boundary. В этой sample coordinator rerun имеет такое пользовательское основание. Текущий harness также уже исключает tests для low-risk reversible changes; его более высокий приоритет ограничивает generic TDD. Проблема — конфликт default rules, а не доказанная бесполезность каждого повторного теста.
|
||||
- **Verification or rollback:** сравнить три случая — cosmetic diff, behavior fix, misleading worker report. Первому не добавлять фиктивный test, второму оставить meaningful regression, третий должен вызвать повторную проверку/блокировку. При сомнении в evidence вернуть targeted rerun.
|
||||
- **Related finding IDs:** AGENT-001, AGENT-003, AGENT-004.
|
||||
|
||||
### AGENT-003 — Generic autonomous rulings конфликтуют с approval flow SDD
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `.agents/skills/workflow/subagent-driven-development/SKILL.md:17–31,296–301,366–371,411–428`; `.agents/skills/sdd/openspec-apply-change/SKILL.md:115–120`; `specs/openspec/config.yaml:55–58`; `specs/openspec/schemas/wealth-ledger-sdd-v2/schema.yaml:625–642`; `session.json:57,2977,4073`.
|
||||
- **Claim:** generic controller предлагает самостоятельно решать plan defects/ambiguities и продолжать, тогда как WealthLedger требует остановить downstream при semantic conflict и переутвердить affected artifacts.
|
||||
- **Current mechanism:** user загрузил OpenSpec и subagent workflow вместе. Generic skill объявляет только четыре stop classes и разрешает rulings; OpenSpec отдельно требует surface/ask при scope/design issues. User и worker briefs в sample явно запрещают изменение approved requirements, компенсируя конфликт.
|
||||
- **Impact / actual cost:** дополнительный process text в dispatch и риск неверного continuation/rework при следующем неоднозначном контракте. Нарушение approval в этой sample **не установлено**. **Impact unmeasured**.
|
||||
- **Why now:** противоречие находится в реально загруженной паре workflows, а не в неиспользуемом архиве.
|
||||
- **Minimum change:** сузить generic rulings до implementation choices внутри approved semantics; при SDD ссылаться на существующий project change-control. Убрать универсальную формулу «only four stops» для workflow с внешним approval. Стандартный OpenSpec skill не менять и не форкать.
|
||||
- **Guarantees at risk:** approval provenance, contract compatibility, financial/access invariants и пользовательское управление scope. Никакой token optimization не оправдывает их обход.
|
||||
- **Counterarguments / trade-offs:** автономные локальные решения ускоряют execution и не требуют approval на каждый naming/refactor. Сохранять их для механических решений; semantic changes различать по уже существующей schema, без нового документа принятия решений.
|
||||
- **Verification or rollback:** статический dry-run на двух ситуациях: локальный helper choice продолжает execution; новая wire field/error semantics возвращает к earliest affected artifact. При неоднозначности сохранять downstream stop.
|
||||
- **Related finding IDs:** AGENT-002, AGENT-006.
|
||||
|
||||
### AGENT-004 — Инструкция Node bootstrap недостаточна для non-interactive shell
|
||||
|
||||
- **Decision = FIX; Severity = P2; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `AGENTS.md:26`; `frontend/.ai/conventions/engineering.md:14`; `backend/.ai/conventions/engineering.md:14`; `specs/AGENTS.md:18–19`; `session.json:3420–3434,3517–3533`.
|
||||
- **Claim:** команда `nvm use --silent` предполагает уже загруженную shell function, но tool shell в sample этого не обеспечил.
|
||||
- **Current mechanism:** coordinator вызывает буквально рекомендованную команду в backend cwd; получает `zsh:1: command not found: nvm`, exit 127. Следующий вызов загружает `$HOME/.nvm/nvm.sh`, выбирает local Node и получает 28 passed.
|
||||
- **Impact / actual cost:** подтвержден один failed tool call и следующий исправленный вызов. Записанные tool durations — 13 ms и 1406 ms; это не стоимость reasoning/turn и не прогноз экономии.
|
||||
- **Why now:** это воспроизводимый по trace разрыв между documented command и выбранным tool environment; worker briefs уже содержат bootstrap, но общий entry point — нет.
|
||||
- **Minimum change:** добавить к существующей инструкции короткое условие для non-interactive shell: загрузить установленный `nvm.sh`, затем `nvm use --silent` из repo owner cwd. Не создавать bootstrap script, не менять `.nvmrc`, не ставить Node в этом аудите.
|
||||
- **Guarantees at risk:** pinned local Node и правильный cwd. Их сохранить; использование произвольного системного Node не является исправлением.
|
||||
- **Counterarguments / trade-offs:** в interactive shell исходная команда работает. Не делать безусловный второй bootstrap на каждый вызов, если shell уже предоставляет `nvm`.
|
||||
- **Verification or rollback:** в следующей обычной implementation session проверить bootstrap в том же non-interactive tool shell и local cwd одной безопасной version-командой; этот аудит не запускал Node.
|
||||
- **Related finding IDs:** AGENT-001, AGENT-002.
|
||||
|
||||
### AGENT-005 — Глобальный skill/tool routing шире типичных задач; вклад требует измерения
|
||||
|
||||
- **Decision = INVESTIGATE; Severity = P3; Confidence = Medium; Effort = S.**
|
||||
- **Evidence:** `~/.claude/CLAUDE.md:1–5`; `~/.claude/skills/graphify/SKILL.md:1–4,49–59,65–100`; `~/.config/opencode/opencode.jsonc:84–102`; `~/.cache/opencode/packages/@dietrichgebert/ponytail@latest/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs:54–82`; текущий harness catalog/tool declarations.
|
||||
- **Claim:** есть risk of inefficiency: global graphify description предлагает обработать любую codebase/project-content question через graph, а глобальная короткая инструкция явно называет `/graphify`. В обычном backend/text task также доступны браузерные и remote-repo tools.
|
||||
- **Current mechanism:** graphify description действительно опубликован в текущем skill catalog; в отсутствие графа полный workflow предполагает install/detect/write. Playwright, Chrome DevTools, Gitea и Context7 доступны в текущем tool surface и зарегистрированы global config. Sample не содержит graphify/browser/MCP вызовов.
|
||||
- **Impact / actual cost:** irrelevance/selection risk и потенциальный prompt/startup cost. Tokens tool schemas, install/startup latency и число ошибочных skill invocations **NOT MEASURED**. Существование двух browser tools не доказывает, что один лишний.
|
||||
- **Why now:** уже видна широкая экспозиция; измерение можно сделать без нового tracing stack и без изменения стандартных OpenSpec skills.
|
||||
- **Minimum change:** проверить resolved startup sources; на обычных задачах выбирать skill по требуемому workflow, не по слову «codebase». Для graphify сравнить narrow explicit-query trigger с текущим broad description; для MCP — доступный task-specific набор при запуске, если harness его поддерживает. Менять default registration только после сравнения.
|
||||
- **Guarantees at risk:** browser/a11y proof для UI, актуальные library docs, remote issue/review operations, полезные существующие graph queries. Для задач с такими boundaries инструменты должны оставаться доступны.
|
||||
- **Counterarguments / trade-offs:** MCP может запускаться лениво, tool schemas могут кэшироваться; graph lookup по готовому графу может экономить чтение. Read-only протокол этого аудита исключает graph generation независимо от broad skill trigger.
|
||||
- **Verification or rollback:** один и тот же bounded task в полном и релевантном tool surface; записать resolved sources, first-response tokens, tool calls и outcomes. Без такого A/B автоматическое удаление MCP/graphify не рекомендовано.
|
||||
- **Related finding IDs:** AGENT-002, AGENT-006.
|
||||
|
||||
### AGENT-006 — Обязательный выбор модели не поддержан текущим Task interface
|
||||
|
||||
- **Decision = SIMPLIFY; Severity = P3; Confidence = High; Effort = S.**
|
||||
- **Evidence:** `.agents/skills/workflow/subagent-driven-development/SKILL.md:184–214`; `.agents/skills/workflow/subagent-driven-development/implementer-prompt.md:6–10`; `.agents/skills/workflow/subagent-driven-development/task-reviewer-prompt.md:11–14,190–204`; текущая declaration `functions.task`; `session.json:2975–2987,3282–3293,4071–4082,4511–4523`.
|
||||
- **Claim:** правило «always specify model explicitly» без capability condition невозможно выполнить через текущий Task tool: он принимает description/prompt/subagent_type/task_id/command, но не model.
|
||||
- **Current mechanism:** все девять task inputs в export без `model`; metadata children совпадает с моделью родительского turn, в том числе после смены parent provider/model. Это подтверждает фактическую выборку, но не раскрывает внутренний routing провайдера.
|
||||
- **Impact / actual cost:** невозможно гарантировать заявленную дешевую роль; агент может тратить время на выбор недоступного параметра или ошибочно обещать экономию. Ни невалидного вызова с model, ни финансового ущерба в sample не наблюдается. **Impact unmeasured**.
|
||||
- **Why now:** несоответствие находится в обязательном template и реальном доступном interface, поэтому повторяется при каждой подготовке dispatch.
|
||||
- **Minimum change:** сделать выбор model условным по tool capability; при неподдерживаемом override использовать доступный default и не заявлять оптимизацию тарифа. Не добавлять custom dispatcher/новый subagent type ради обхода.
|
||||
- **Guarantees at risk:** достаточная модель для сложного review и честное описание фактических capabilities. Не обещать escalation, которую tool не может исполнить.
|
||||
- **Counterarguments / trade-offs:** другой harness может поддерживать model override; правило там полезно. Историческая Task schema не сохранена, поэтому невозможность override доказана для текущего interface, а для sample виден только фактический default routing.
|
||||
- **Verification or rollback:** сверить template с tool schema; следующий нужный dispatch должен быть валидным без выдуманного параметра. При доступном override правило можно применять как раньше.
|
||||
- **Related finding IDs:** AGENT-003, AGENT-005.
|
||||
|
||||
## 7. Observed Session Anti-patterns и контрпримеры
|
||||
|
||||
**Goal → reads → tools → result:** explicit stock-screener SDD/delegation request (`session.json:57`) → store/list/status, approval lookup и tasks → user approval → apply contextFiles → семь дополнительных planning/contract reads, repo entry points → два explore и generic workflow setup → task 1.1 implementer/reviewer → coordinator target check → checkbox/ledger → task 1.2 dispatch, interruption/re-dispatch, review interruption/re-dispatch → выявленный contract defect → fix running при окончании export.
|
||||
|
||||
| Наблюдение | Evidence | Вывод с границами |
|
||||
| --- | --- | --- |
|
||||
| Generic workflow дает большой payload | Skill output `session.json:2251–2256`; размер 33577 bytes | Содержимое реально попало в parent context; его token attribution не измерена |
|
||||
| Explore возвращают подробную карту в parent | `:2276–2330`; суммарно 25197 bytes | Можно сузить будущий запрос до owner/entry points/named risks; нельзя объявить всю карту ненужной для сложной feature |
|
||||
| Worker full verify заявлен дважды | `:2980`, `:4076`; task-2 report сохранен в `:4176–4184` | Per-worker требование видно; реальные commands/timings children недоступны |
|
||||
| Fresh coordinator test после review | `:3380–3428,3517–3533` | Наблюдаемое повторное исполнение; user explicit verification — существенный контраргумент |
|
||||
| Non-interactive bootstrap retry | `:3421–3428,3519–3526` | Подтвержденная ошибка, AGENT-004 |
|
||||
| Два canceled task заменены свежими dispatch | `:3929–3979,4072–4089,4371–4410,4513–4530` | Отмена/повтор подтверждены. Потеря partial edits, корень отмен, crash модели и возможность safe resume UNVERIFIED |
|
||||
| Последующий fix использует resume | `:4620–4632` | Уже есть экономящий context механизм; результат fix в export отсутствует |
|
||||
| Read loops | Group by `.state.input.filePath` | Не найдены в parent. Нельзя переносить вывод на children |
|
||||
| Review выявляет реальный mismatch | `:4517` | Зеленые worker tests не заменяют semantic review. Здесь удалить review нельзя |
|
||||
|
||||
Начальные store/status/apply commands и чтение всех возвращенных contextFiles соответствуют explicit user request и стандартному skill. Одинаковые слова `verify`, `AGENTS`, `contract` в разных источниках сами по себе не доказательство DRY-нарушения.
|
||||
|
||||
Смена модели после interruption сопровождается parent token counters без cache reuse (`session.json:4026–4034`), позже — частичным cache read (`:4467–4475`). Это наблюдение, **не доказательство**, что смена модели вызвала потерю кэша или что непрерывный сеанс стоил бы меньше.
|
||||
|
||||
## 8. Minimal Agent Execution Policy
|
||||
|
||||
Это рекомендуемое сокращение существующей процедуры после согласования AGENT-001/002, а не новый обязательный документ. Сегодня действующие repo gates и explicit task Verification имеют силу.
|
||||
|
||||
Общий путь: **read once → narrow search → edit batch → targeted check → final affected-repo gate**. Повторный routing — проверка новых границ, а не перечитывание уже актуального файла. Новый Read нужен при смене revision, неполном excerpt или появлении новой ответственности.
|
||||
|
||||
| Размер / тип | Минимальная последовательность | Verification и stop boundaries |
|
||||
| --- | --- | --- |
|
||||
| S — small UI fix | Явный frontend cwd/status → local AGENTS + engineering entry point → affected component/CSS/i18n и 1 ближайший пример → UI или только strings section; TS/testing темы по реальному diff → одна batch правка | Targeted type/lint, semantic visual/keyboard/narrow viewport по изменению; behavior test только при risk. Full final gate по действующей/согласованной policy. Не читать backend/docs/specs ради чисто визуального diff, не открывать SDD lifecycle |
|
||||
| M — backend contract change | Backend cwd/status → AGENTS/engineering → HTTP API, affected DTO/controller/service, configured HTTP tests → согласованная wire expectation и опубликованный baseline/artifact → errors/security/data только по trigger → batch implementation | Type/lint + relevant HTTP outcome + `test:openapi-contract`; генерация публичного artifact. Money/CAS/transaction changes требуют своей real-boundary proof. Передать artifact потребителю; affected frontend отдельно проверяет snapshot/generated types и relevant behavior. Не читать весь consumer checkout для проектирования DTO |
|
||||
| L — SDD feature | Explicit store selection/status/apply и approvals → все native contextFiles прочитать один раз → dependency/owner map → local AGENTS только affected repo → task-specific upstream sections/contracts → implementation по dependencies; делегация только если явно запрошена/разрешена | Targeted proof в worker; один read-only scoped reviewer на оговоренную границу; fix только findings и covering tests; full verify на итоговом состоянии affected repo с дополнительными contract/DB/auth checks. До task checkbox — approved task Verification; после — refresh native apply instructions. Final semantic comparison target ↔ generated API, docs/QA по approved tasks |
|
||||
|
||||
**Что действительно нельзя пропустить:** измененный response/status/error shape требует producer/consumer contract lookup, даже если UI diff маленький. Изменение auth/connection cache требует проверки владельца session/generation. Для утвержденного SDD нельзя заменить contextFiles догадкой или пропустить upstream requirement ради smaller context. Structural validation не доказывает runtime contract compatibility.
|
||||
|
||||
**Контекст соседей:** фактическое число нерелевантных прочитанных файлов определить нельзя. В sample оба application repo и docs входят в explicit delivery scope, поэтому их AGENTS не являются ошибочным чтением. В S UI flow идеальная выборка не требует чтения трех соседних checkout; в M она требует предоставленного API artifact и конкретных consumer expectations, а не исходников всех соседей.
|
||||
|
||||
## 9. Keep/Delete/Move Instruction Register
|
||||
|
||||
| Instruction / mechanism | Решение | Owner / куда относится | Основание |
|
||||
| --- | --- | --- | --- |
|
||||
| Local AGENTS task/path routing | KEEP | Каждый repo | Самостоятельность и selective context уже реализованы; root `AGENTS.md:9–13` |
|
||||
| Одинаковые engineering базовые нормы frontend/backend | KEEP | Оба автономных repo | Общий root файл создал бы обязательную cross-checkout зависимость; одинаковый текст здесь не доказательство лишнего механизма |
|
||||
| Безусловное install для любого code change | SIMPLIFY | Существующий local engineering section | Environment need отделить от source diff, AGENT-001 |
|
||||
| Полный gate на каждую мелкую worker task | SIMPLIFY | Repo delivery owner и approved task Verification | Сохранять final proof; не дублировать по факту слова DONE |
|
||||
| «Каждая функция обязана иметь новый test», «fresh в этом message» | SIMPLIFY | Generic skill applicability | Local risk boundary + evidence version, AGENT-002 |
|
||||
| Autonomous ruling над SDD semantics | FIX | Generic coordinator, project change-control остается owner | AGENT-003; стандартные OpenSpec skills сохраняются |
|
||||
| Обязательное поле model при отсутствии capability | SIMPLIFY | Generic templates | AGENT-006 |
|
||||
| Manual style/sorting/import-rule перепроверка | MOVE responsibility | Существующие ESLint/TypeScript/dependency configs | AGENT prompt оставляет короткий trigger и ссылку; не дублирует deterministic analyzer вручную |
|
||||
| Смысл owner, public error safety, product i18n | KEEP | Semantic review и local conventions | Lint не доказывает смысл; UI `:157–170`, architecture `:86–93` |
|
||||
| Finance page container/VM/view | KEEP | Frontend UI owner | UI `:44–70`; AccountIncomePage `:8–14` — state selection, не пустой wrapper для каждого control |
|
||||
| Запрет passthrough hooks и ненужных VM/wrapper файлов | KEEP | Frontend UI owner | `frontend/.ai/conventions/ui.md:26–28,84–105` уже запрещает механическое дробление; конкретного требования/объекта для удаления не найдено |
|
||||
| FSD skill и local placement profile | KEEP с selective references | Local architecture решает placement | Skill `:24–29,33–50,76–77` допускает selective reading и existing widgets; автоматическая миграция не нужна |
|
||||
| Explicit SDD, approvals, store, contextFiles | KEEP | Specs config/schema + native skill | Project semantic authority и защита cwd/store; не переписывать standard skill ради меньшего payload |
|
||||
| Progress/checkpoint и scoped review | KEEP | Уже выбранный workflow | Защита от повторной работы/пропуска contract; bounded brief/report может быть полезен |
|
||||
| Глобальные graphify/MCP declarations | INVESTIGATE | Harness/global config | Startup/capability cost не измерен, AGENT-005 |
|
||||
| Boy Scout | KEEP | Affected area review | Root code-health `:30–37`, engineering `:25–27` ограничивают low-risk scope; не повод для whole-repo cleanup |
|
||||
|
||||
### DELETE / SIMPLIFY / KEEP / INVESTIGATE — сводка
|
||||
|
||||
| Decision | Объекты | Безопасная граница |
|
||||
| --- | --- | --- |
|
||||
| DELETE | Подтвержденных standalone механизмов для удаления нет | Не удалять проверку только из-за ее размера/повтора названия |
|
||||
| SIMPLIFY | AGENT-001, AGENT-002, AGENT-006 | Reuse environment и evidence, final gate и meaningful regression остаются |
|
||||
| FIX | AGENT-003, AGENT-004 | Approval precedence и pinned Node сохраняются |
|
||||
| KEEP | Local routers; native OpenSpec; target/runtime API comparison; auth/money/real DB/CAS/rollback checks; scheduler lifecycle/idempotency/replay proof по trigger; scoped semantic review | Protected invariants не торгуются на tokens; их runtime реализации в этом аудите не переоценивались |
|
||||
| INVESTIGATE | AGENT-005; cancellation recovery; child overhead | Только bounded measurement, без нового tracing framework |
|
||||
|
||||
Ponytail-ranked cuts: `shrink:` объединить final verification на repo delivery boundary [AGENT-001]; `shrink:` убрать конкурирующие blanket testing/freshness mandates [AGENT-002]; `yagni:` убрать обязательный неподдерживаемый model override [AGENT-006]. **Net lines/deps possible: not measured; удаление dependencies не предложено.**
|
||||
|
||||
## 10. Cost & benefit
|
||||
|
||||
| Изменение | Класс ожидаемой экономии | Фактическая опора | Необходимые гарантии |
|
||||
| --- | --- | --- | --- |
|
||||
| AGENT-001 | Local runtime, agent turns, install traffic | Два worker full-verify reports; экономия not measured | Fresh final verify, dependency reproducibility, targeted risk suites |
|
||||
| AGENT-002 | Agent tokens/tool calls, test maintenance | Coordinator rerun виден; непродуктивные новые tests не доказаны | Версия evidence, meaningful RED/GREEN, independent semantic review |
|
||||
| AGENT-003 | Cognitive load, dispatch prompt, rework | Конфликт реально загруженных instructions | Approved semantics, safe escalation |
|
||||
| AGENT-004 | Retry/tool call и associated reasoning | Один failed call + corrected run | Local pinned Node/cwd |
|
||||
| AGENT-005 | Возможные prompt tokens/startup latency | Global registration/catalog exposure | Нужные browser/docs/remote tools по actual task |
|
||||
| AGENT-006 | Planning overhead, честность cost policy | Template требует отсутствующую capability | Валидный task interface и достаточная model capability |
|
||||
|
||||
**CI minutes:** этот аудит не обосновывает сокращение CI. Рассматриваются причины, по которым агент вызывает checks локально; изменение состава pipeline — audit 11. Проценты, ускорение, денежная экономия и сокращение строк не рассчитывались.
|
||||
|
||||
## 11. Top actions
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **AGENT-004:** дополнить существующий Node entry point non-interactive bootstrap условием; проверить один реальный tool shell без нового скрипта.
|
||||
2. **AGENT-006:** заменить безусловный model override на capability-aware правило; убрать невыполнимое обязательство из templates.
|
||||
3. **AGENT-003:** явно подчинить generic rulings существующему SDD change-control; semantic conflicts должны останавливать affected downstream.
|
||||
4. **AGENT-002:** согласовать generic TDD applicability с local testing; повторную проверку обосновывать изменением diff/named doubt или explicit user requirement.
|
||||
|
||||
### Решения с подтверждением проверки
|
||||
|
||||
5. **AGENT-001:** согласовать install/targeted/final verification tiers в двух существующих engineering sections. Сначала сохранить full final gate и сравнить реальные вызовы на одной bounded delivery; explicit approved task checks не сокращать автоматически.
|
||||
6. **AGENT-005:** снять resolved instruction/tool surface для одного запуска, затем A/B релевантного MCP/skill набора. Изменение default global config — только при наблюдаемом выигрыше и сохраненных outcomes.
|
||||
|
||||
Новый ADR для текста bootstrap или capability caveat не нужен. Если меняется принятая модель SDD approvals/verification authority, сначала нужен owner decision в существующем process source; не создавать отдельную approval инфраструктуру.
|
||||
|
||||
## 12. Cross-audit signals
|
||||
|
||||
- **Audit 11:** возможные повторы build/dependency graph внутри local verify/commit пути — `frontend/package.json:14–34`, `.husky/pre-commit:1–3`; здесь установлены только agent/hook triggers.
|
||||
- **Audit 10:** пригодность generic workflow scripts для no-commit/multi-repo execution — `.agents/skills/workflow/subagent-driven-development/scripts/`; script semantics не исследовались.
|
||||
- **Audit 15:** project SDD artifact/approval granularity и `skip_specs` lifecycle — `specs/README.md:9–14`, v2 `schema.yaml`; текущий аудит не предлагает удалить согласованные артефакты.
|
||||
- **Audit 13:** хранение credentials в global agent config — `~/.config/opencode/opencode.jsonc`; значения не воспроизводятся, отдельное security расследование не проводилось.
|
||||
|
||||
## 13. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Самый дешевый способ проверки | До проверки |
|
||||
| --- | --- | --- |
|
||||
| Сколько full verify/install реально запускают children? | Для следующей одной affected-repo delivery предоставить child export/command log с command, cwd, exit, duration и revision; не собирать все личные sessions | AGENT-001 static finding остается; численную экономию не заявлять |
|
||||
| Есть ли безопасный resume после двух cancellations? | Проверить конкретные child status/report/diff до re-dispatch; сопоставить canceled и replacement task IDs | Не объявлять отмененную task DONE и не удалять checkpoint |
|
||||
| Почему global CLAUDE и skill directory попадают в OpenCode? | Startup resolved-source/config diagnostic для этого cwd без provider secrets; сравнить с loaded declarations | Loader fallback UNVERIFIED; не утверждать наличие symlink или mandatory global reads |
|
||||
| MCP startup/tool schema или skill bodies доминируют в первом prompt? | Один bounded A/B: одинаковая task, model, repo revision; export первых turn counters и tool catalog | Не удалять серверы по количеству зарегистрированных tools |
|
||||
| Дает ли новый verification tier ту же защиту? | Итоговый full verify + applicable HTTP/DB/auth tests на том же конечном diff; убедиться, что dependency/artifact state fresh | Текущие mandatory checks сохраняются |
|
||||
| Возможен ли model override в выбранном runtime? | Текущая tool schema и documented runtime capability; никаких проб с выдуманным parameter | AGENT-006 применим для текущего interface; историческая schema UNVERIFIED |
|
||||
|
||||
Для минимального последующего измерения достаточно одного существующего session export на каждый из S/M/L типов: tool counts; unique/repeated reads; failed/retried commands; checks по revision; `.info.tokens` отдельно input/cache/output/reasoning; parent/child boundaries; completion outcome. Тела secrets/PII и сырые приватные conversations не нужны. Новые hooks, scripts или multi-agent observability infrastructure не требуются.
|
||||
|
||||
### Проверка результата аудита
|
||||
|
||||
Выполнена ручная смысловая сверка отчета с evidence. `git diff --check` и отдельный `git diff --no-index --check /dev/null audit-results/14-ai-agent-workflow-efficiency.md` завершились без замечаний; отдельная проверка нужна, поскольку файл ignored и обычный diff его не видит. `git check-ignore -v` подтвердил root правило `.gitignore:2`. Финальные `git status --short` четырех checkout пусты, HEAD совпадают с начальной таблицей. Standard OpenSpec skills и исходники не редактировались; tests/build/verify не запускались. Application gates не применяются к единственному Markdown-отчету в root.
|
||||
|
||||
## Consolidation table
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| AGENT-001 | SIMPLIFY | P2 | High | frontend,backend | frontend/.ai/conventions/engineering.md; backend/.ai/conventions/engineering.md; frontend/package.json; backend/package.json; session.json | Безусловный install/full verify переносится в каждый worker; экономия не измерена | S | AGENT-002,AGENT-004 |
|
||||
| AGENT-002 | SIMPLIFY | P2 | High | root,frontend,backend | .agents/skills/workflow/test-driven-development/SKILL.md; .agents/skills/workflow/verification-before-completion/SKILL.md; .agents/skills/workflow/subagent-driven-development/task-reviewer-prompt.md; frontend/.ai/conventions/testing.md; backend/.ai/conventions/testing.md; session.json | Конкурирующие blanket testing/fresh-evidence rules затрудняют reuse proof | S | AGENT-001,AGENT-003,AGENT-004 |
|
||||
| AGENT-003 | FIX | P2 | High | root,specs | .agents/skills/workflow/subagent-driven-development/SKILL.md; .agents/skills/sdd/openspec-apply-change/SKILL.md; specs/openspec/config.yaml; specs/openspec/schemas/wealth-ledger-sdd-v2/schema.yaml; session.json | Generic autonomous rulings конфликтуют с approved semantic change-control | S | AGENT-002,AGENT-006 |
|
||||
| AGENT-004 | FIX | P2 | High | root,frontend,backend,specs | AGENTS.md; frontend/.ai/conventions/engineering.md; backend/.ai/conventions/engineering.md; specs/AGENTS.md; session.json | Недостаточный nvm bootstrap вызвал подтвержденный shell retry | S | AGENT-001,AGENT-002 |
|
||||
| AGENT-005 | INVESTIGATE | P3 | Medium | global | ~/.claude/CLAUDE.md; ~/.claude/skills/graphify/SKILL.md; ~/.config/opencode/opencode.jsonc | Широкая skill/tool экспозиция создает risk of inefficiency; стоимость не измерена | S | AGENT-002,AGENT-006 |
|
||||
| AGENT-006 | SIMPLIFY | P3 | High | root | .agents/skills/workflow/subagent-driven-development/SKILL.md; .agents/skills/workflow/subagent-driven-development/implementer-prompt.md; .agents/skills/workflow/subagent-driven-development/task-reviewer-prompt.md; session.json | Обязательный model override отсутствует в текущем Task interface | S | AGENT-003,AGENT-005 |
|
||||
@@ -0,0 +1,375 @@
|
||||
# Аудит 15 — SDD, Specs & Documentation
|
||||
|
||||
## 1. Audit metadata
|
||||
|
||||
- **Дата:** 11 октября 2026 года.
|
||||
- **Задание:** `audit-prompts/15-sdd-specs-and-documentation.md`.
|
||||
- **Результат:** `audit-results/15-sdd-specs-and-documentation.md`. Имя выбрано по прямому указанию пользователя; файл находится вне четырёх предметных репозиториев.
|
||||
- **Режим:** read-only для `frontend`, `backend`, `specs`, `docs`; создан только этот отчёт. Исходники, конфигурации, тесты, архивы, generated artifacts и зависимости не изменялись.
|
||||
- **Skill:** установленный `ponytail-audit` прочитан и применён к стоимости документационной сложности. Его область — упрощение; расхождения требований, контрактов и lifecycle проверены дополнительно по протоколу задания. `verification-before-completion` применён к проверке результата. `graphify` прочитан: существующий `graphify-out/graph.json` не найден, граф не создавался из-за read-only границы и запрета дополнительных артефактов.
|
||||
- **Предыдущий аудит:** `audit-results/technical-audit-2026-10-11.md` использован как реестр гипотез, не как доказательство. Его runtime findings не переносились в этот отчёт.
|
||||
|
||||
### Репозитории и точные revisions
|
||||
|
||||
Workspace: `/Users/ksv741/Project/vibe/WealthLedger`.
|
||||
|
||||
| Repo | Найденный путь от workspace | HEAD | Рабочее дерево |
|
||||
| --- | --- | --- | --- |
|
||||
| root | `.` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | Чистое; `audit-results` исключён root allowlist |
|
||||
| frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | Чистое |
|
||||
| backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | Чистое |
|
||||
| specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | Чистое |
|
||||
| docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | Чистое |
|
||||
|
||||
`git status --porcelain=v1` проверен до исследования и перед записью. Итоговый повтор и проверка отчёта зафиксированы в разделе 9. Чистый status root сам по себе не подтверждает сохранность ignored nested repositories, поэтому они проверяются отдельно.
|
||||
|
||||
### Фактический layout и tooling
|
||||
|
||||
- `specs/openspec/` — единственный найденный store; config выбирает `wealth-ledger-sdd-v2`, change metadata сохраняет выбор конкретной schema.
|
||||
- Backend — NestJS 12, Prisma 7.10, SQLite, TypeScript 6; frontend — React 19.3, Vite 8.3, TanStack Query 5, TypeScript 5.9. Источники: manifests, без проверки всех установленных runtime-версий.
|
||||
- `docs` — Markdown и Mermaid без настроенного публикационного генератора; `specs` — OpenSpec и Git tooling.
|
||||
- Найден executable `openspec`: `/Users/ksv741/.nvm/versions/node/v24.21.0/bin/openspec`. Установленный package manifest содержит `@fission-ai/openspec` **1.13.1**. Это статическое чтение manifest, не результат запуска `openspec --version`.
|
||||
- Далее **`$OPENSPEC`** — обозначение каталога `/Users/ksv741/.nvm/versions/node/v24.21.0/lib/node_modules/@fission-ai/openspec`, а не созданная конфигурация проекта.
|
||||
|
||||
### Выполненные проверки и ограничения
|
||||
|
||||
Выполнены безопасные чтения и поиск, `git rev-parse HEAD`, `git status`, выборочные `git log`, `git show --stat`, `git ls-files`, подсчёты через `rg -c`, `git diff --check`, а также:
|
||||
|
||||
```bash
|
||||
git diff --no-index --stat backend/openapi.json frontend/src/shared/api/spec/openapi.json
|
||||
```
|
||||
|
||||
Результат сравнения: exit 0, различий файлов на диске нет. Это **не** подтверждает свежую генерацию относительно handlers, совместимость с target contract или совместимость развёрнутой пары приложений.
|
||||
|
||||
**Не запускались:** application tests, full-suite/build/verify, OpenSpec validation/status/archive/sync, codegen, install, миграции, браузер, внешние API или сетевые проверки URL. Не создавались инструменты аудита. Чтение имеющихся tests подтверждает содержание assertions, но не их успешное исполнение.
|
||||
|
||||
## 2. Executive verdict
|
||||
|
||||
1. **Факт — разделение authority в целом удачное.** Требования принадлежат `specs`, объяснения текущей системы — тематическим docs, причины решений — ADR, executable HTTP contract — backend metadata/OpenAPI, frontend получает отдельный snapshot. Второй постоянный editable OpenAPI в docs/specs не найден в исследованной области. Это разделение следует сохранить.
|
||||
2. **Факт — нормативная документационная policy не согласована с текущим docs repo.** Canonical `project-documentation` требует `docs/features`, audit snapshots и remediation registry, тогда как действующие docs instructions запрещают исследовательские отчёты и используют тематические страницы. Наибольшая польза — согласовать норму, а не восстановить старые деревья (**SPEC-001**).
|
||||
3. **Факт — архивная метка не доказывает завершение.** `reconcile-hourly-investment-sync` находится в archive с 10 unchecked tasks и Completion Evidence о завершении. Установленные native archive и стандартный skill допускают confirmation для incomplete tasks; проектная spec обещает более строгий запрет. Причина конкретного архивирования **UNVERIFIED**, противоречие сохранённых records подтверждено (**SPEC-002**).
|
||||
4. **Факт — обновлённые cross-cutting docs сосуществуют с устаревшим текущим объяснением.** Обзор портфеля описывает минутную свежесть и прежний refresh, страница акций — прежний background-reference путь рядом с online appendix. Это current-doc drift, а не основание переписывать исторический ADR 017 (**SPEC-003**).
|
||||
5. **Факт — основной specs router пропустил несколько текущих продуктовых тем.** В `AI_INDEX` отсутствуют прямые входы в банковские карты, hourly sync, data sync, online reference и value history. Это затрудняет адресное чтение требований после недавних поставок (**SPEC-004**).
|
||||
6. **Факт и риск — одинаковые OpenAPI copies не закрывают target compatibility.** У скринера опубликованная schema теряет `additionalProperties: false` и `lot.minimum: 1` из target contract. Прочитанный parity test сравнивает генератор с runtime metadata, не с target. Реальная выдача некорректного lot и обработка неизвестного поля в живом pipeline **не проверены** (**SPEC-005**).
|
||||
|
||||
**Итог:** пять подтверждённых локальных findings уровня P2; P0/P1 в этом scope не установлены. Массовое переписывание docs, сокращение стандартных OpenSpec skills, удаление archives или новый documentation generator не обоснованы. Runtime/CI экономия не измерена; ожидаемая польза — меньше ошибочных решений и повторного выяснения authority.
|
||||
|
||||
## 3. Inventory & coverage
|
||||
|
||||
### Метрики и метод подсчёта
|
||||
|
||||
| Объект | Результат | Метод и границы |
|
||||
| --- | --- | --- |
|
||||
| Canonical capability specs | 30 | `git ls-files 'openspec/specs/*/spec.md'` и количество строк через `rg -c '.'`; tracked specs в данном checkout |
|
||||
| Change metadata | 62 | Tracked `openspec/changes/*/.openspec.yaml`; 61 путь под `archive/`, 1 вне archive |
|
||||
| Active change | `stock-screener` | Единственная metadata вне archive; schema v2, created 2026-10-10 |
|
||||
| Docs Markdown | 50 | `git ls-files '*.md'` и `rg -c '.'`; включая README, AGENTS, CONTRIBUTING, CHANGELOG и исторический отчёт |
|
||||
| ADR records | 17 | Tracked `adr/*.md`, имена с числовым префиксом; `adr/README.md` исключён |
|
||||
| Прямые canonical links в AI_INDEX | 16 | `rg --only-matching 'openspec/specs/[^)]+/spec\.md' AI_INDEX.md` и количество строк; это число ссылок, не оценка всей косвенной достижимости |
|
||||
| Unchecked tasks в выбранном reconciliation archive | 10 | `rg -c '^- \[ \]' .../tasks.md`; только этот файл, не все архивы |
|
||||
| Checked tasks активного скринера | 8 | `rg -c '^- \[x\]' openspec/changes/stock-screener/tasks.md`; checkbox не доказывает acceptance |
|
||||
|
||||
Это inventory по tracked путям и адресная содержательная выборка. Полный semantic review всех 30 specs, 61 archives и 50 Markdown-файлов не выполнялся. Число файлов не использовано как доказательство избыточности.
|
||||
|
||||
### Source-of-Truth Authority Matrix
|
||||
|
||||
| Знание | Authority и статус | Owner / consumer | Что доказывает и чего не доказывает |
|
||||
| --- | --- | --- | --- |
|
||||
| Intent текущей обычной задачи | Прямой запрос пользователя; current | Пользователь → implementation owner | Задаёт scope; не запускает SDD автоматически (`specs/AGENTS.md:3–6`) |
|
||||
| Intent OpenSpec change | `proposal.md`; draft/approved определяется явным решением | Change Owner → Analyst/engineers | Наличие файла не доказывает approval |
|
||||
| Сохранённое behavioral requirement | `specs/openspec/specs/*/spec.md`; нормативный current store | Product/System Analyst, для процесса Engineering Owner → реализация/review | Согласованная норма; может оказаться устаревшей или противоречивой |
|
||||
| Поведение ещё не закрытого change | Active delta specs; target | Change Owner/Analyst → affected engineers и QA | До sync/archive не является автоматически canonical current capability |
|
||||
| Target interface | Change-local `system-analysis/contracts/`; target, после archive historical | System Analyst + producer/consumer owners | Планирует межсистемный contract; не второй постоянный current OpenAPI |
|
||||
| HTTP shape реализации | Backend controllers/DTO/Swagger metadata; current | Backend → consumers, generator, HTTP tests | Генерация отражает metadata, не автоматически все фактические response branches |
|
||||
| Публикуемый OpenAPI | `backend/openapi.json`; generated, Git-ignored по локальной policy | Backend → явная передача consumer | Совпадение файла с consumer не доказывает происхождение от текущего source |
|
||||
| Consumer contract | `frontend/src/shared/api/spec/openapi.json`; versioned snapshot | Frontend → generated types и client | Локальный предоставленный вход; не второй независимо редактируемый API design |
|
||||
| Generated transport types | `frontend/src/shared/api/generated/schema.d.ts`; derived | Generator → frontend source/typecheck | Производны от snapshot; не runtime-validation произвольного ответа |
|
||||
| Current architecture/product explanation | `docs/architecture`, `domains`, `integrations`, `glossary`; current | Автор затронутого изменения → contributor/support | Самостоятельное объяснение; расхождение с code/spec требует обозначения |
|
||||
| Architectural rationale | `docs/adr`; historical decision с current/superseded частями | Engineering Owner → будущий change | История решения; Accepted baseline не является полным current reference |
|
||||
| Реальный механизм | Source/config/Prisma schema; current checkout | Owner соответствующего module/slice → инженеры | Позволяет вывести поведение; не подтверждает deployment или все runtime cases |
|
||||
| Regression / acceptance proof | Assertions и наблюдённый результат на нужном revision | Developer tests + QA/reviewer | Зелёный structural/parity gate не подменяет semantic acceptance |
|
||||
| Completion record | Archived `completion-evidence.md`; historical evidence | Finalization owner → reviewer/contributor | Должен отделять observed факты, ограничения и незавершённую работу |
|
||||
| Coding/process instructions | Repo-local AGENTS и conventions; current локальная policy | Owner repo → участники разработки | Не обязаны читать соседний checkout; конфликт с canonical process spec остаётся предметом согласования |
|
||||
|
||||
Основания: `specs/README.md:3–31`, `specs/openspec/config.yaml:17–43,55–69`, `docs/CONTRIBUTING.md:7–26,58–79,122–144`, `backend/.ai/conventions/http-api.md:69–88`, `frontend/.ai/conventions/data.md:15–33`.
|
||||
|
||||
### Фактический OpenSpec lifecycle
|
||||
|
||||
- Legacy `wealth-ledger-sdd`, schema version 5: proposal → specs → design → tasks → apply. Выбранный банковский archive действительно сохраняет legacy `.openspec.yaml`.
|
||||
- Default/new `wealth-ledger-sdd-v2`, schema version 8: proposal → specs → system-analysis → design + qa-analysis → docs-impact → tasks → apply. У stock screener и reconciliation archive выбрана v2.
|
||||
- V2 schema отдельно задаёт observable delta, target contract, internal design, system QA и docs promotion. `skip_specs: true` предусмотрен для отсутствия behavioral delta; фальшивый Requirement не требуется (`specs/README.md:14`).
|
||||
- Native delta application обнаруживает change-local specs и сопоставляет их canonical target paths: `$OPENSPEC/dist/core/specs-apply.js:67–103`. Archive вызывает тот же механизм и имеет отдельный skip-specs путь: `$OPENSPEC/dist/core/archive.js:1158–1169`.
|
||||
- Project docs/config требуют semantic verification и docs delivery до archive; native incomplete-task confirmation не является такой проверкой. Различие — **SPEC-002**.
|
||||
- Скринер имеет implementation и docs в текущих commits, но остаётся active. Само отсутствие canonical `stock-screener/spec.md` **не finding**: approvals/независимая QA и готовность Finalization в этой сессии не подтверждались. Автоматический sync/archive не предлагается.
|
||||
|
||||
### Spec-to-Implementation Traceability Sample
|
||||
|
||||
Выборка risk-based: последняя реализованная v2 функция, недавно архивированное системное изменение границы чтения и legacy денежная функция. Она проверяет разные стадии lifecycle и разные consumers, но не репрезентативна для всех функций.
|
||||
|
||||
| Use case / requirement | Design / ADR / contract | Реализация и consumer | Test proof, прочитанный статически | Docs / результат |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Скринер: полный каталог, совместные фильтры, отдельный выпуск, стабильные страницы — active `stock-screener/specs/stock-screener/spec.md:7–76` | Active `system-analysis/analysis.md:24–57`; `design.md:37–67`; target OpenAPI:7–66,92–177. ADR обоснованно N/A (`docs-impact.md:22–24`) | Backend `stock.controller.ts:85–109` → `stock-screener.service.ts:65–109`; selector:21–105. Frontend `StocksPage.vm.ts:29–55,58–115` использует generated routes | Service tests:40–77 проверяют неизменный `asOf`, 30+1 страницу, повторный search, пустоту и ownership/revision; HTTP `stocks.e2e-spec.ts:65–105`; frontend page tests:71–86,127–172. Не запускались | `docs/domains/instruments/stocks.md:31–53` и `docs/architecture/http-api.md:24–33` отражают новый поток. Target constraints теряются в schema — SPEC-005. Active status сохранён |
|
||||
| Локальное чтение Portfolio независимо от hourly attempt — canonical `portfolio-view/spec.md:9–16,96–124`, `hourly-investment-sync/spec.md:27–40` | Reconciliation archive: target envelope и docs-impact:14–19; ADR 019:9–29 | `portfolio.service.ts:55–110,113–198` читает local access/repository и наблюдение hourly; consumer contract `schema.d.ts:1051` задаёт resource envelope | `tbank-local-reads.e2e-spec.ts:167–234` публикует snapshot, затем проверяет warm GET без дополнительных GetAccounts/metadata/decrypt и отсутствие revision/token в JSON. Не доказывает весь scheduler | `portfolio-loading.md:10–22,34–43` и freshness page:3–12,40–60 актуальны по выбранной границе. Overview не приведён к ним — SPEC-003; archive task evidence противоречив — SPEC-002 |
|
||||
| Value history: точка только полной оценки, без нового публичного чтения — `portfolio-value-history/spec.md:8–28` | ADR 019:17–26; reconciliation docs-impact:45–50 сознательно не создаёт публичную страницу истории | `portfolio.repository.ts:116–183`: fenced transaction, upsert snapshot, условная запись points по attempt/currency | `tbank-value-history.e2e-spec.ts:125–201` проверяет две валюты и отсутствие points при неизвестной цене. Suite не запускался; scheduler в setup подменён (:47–53) | Отсутствие history UI/doc tutorial не дефект: публичный read не требуется. Нужно различать внутренние points и отсутствующий пользовательский график, не обещать новую функцию |
|
||||
| Карты: расходы/возвраты/net, возврат в своём периоде, исключение переводов — `manual-bank-cards/spec.md:448–492` | Legacy `add-manual-bank-cards/design.md:82–126`; постоянный ADR N/A допустим, storage rationale остаётся в Design и общих ADR | Controller `bank-cards.controller.ts:127–130` → `get-bank-summary.use-case.ts:12–37` → `bank-card.calculations.ts:59–111`; DTO consumer `schema.d.ts:810–827` | Domain test:74–137 проверяет отрицательный net и исключение transfers/topups; frontend `BankSummary.view.test.tsx:9–33` показывает три точных итога. Не запускались | `docs/domains/cards/overview.md:80–97` объясняет три суммы и месяцы возврата. В этой выборке семантика согласована; отсутствие прямого входа в spec — SPEC-004 |
|
||||
|
||||
В таблице использованы следующие корни сокращённых paths:
|
||||
|
||||
- Canonical requirements: `specs/openspec/specs/`; активный скринер: `specs/openspec/changes/stock-screener/`; reconciliation archive: `specs/openspec/changes/archive/2026-10-09-reconcile-hourly-investment-sync/`; банковский Design: `specs/openspec/changes/archive/2026-10-09-add-manual-bank-cards/design.md`.
|
||||
- Backend stocks: `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts`, `application/stock-screener.service.ts`, `application/stock-screener.service.spec.ts`, `domain/stock-screener-selection.rules.ts` относительно того же `stocks/`.
|
||||
- Backend Portfolio: `backend/src/modules/investments/portfolio/application/portfolio.service.ts`, `backend/src/modules/investments/portfolio/infrastructure/portfolio.repository.ts`.
|
||||
- Backend banking: `backend/src/modules/banking/bank-cards/presentation/bank-cards.controller.ts`, `backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.ts`, `backend/src/modules/banking/bank-cards/domain/bank-card.calculations.ts`, `backend/src/modules/banking/bank-cards/domain/bank-card.calculations.spec.ts`.
|
||||
- Backend HTTP/SQLite suites: `backend/test/stocks.e2e-spec.ts`, `backend/test/tbank-local-reads.e2e-spec.ts`, `backend/test/tbank-value-history.e2e-spec.ts`.
|
||||
- Frontend stock source/tests: `frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts`, `frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx`; банковский test: `frontend/src/entities/bank-card/ui/BankSummary/BankSummary.view.test.tsx`; transport types: `frontend/src/shared/api/generated/schema.d.ts`.
|
||||
- Docs: `docs/domains/portfolio/flows/portfolio-loading.md`, `docs/architecture/cross-cutting/data-freshness.md`, `docs/adr/019-hourly-reads-and-online-instrument-reference.md`.
|
||||
|
||||
Фактическая успешность всех перечисленных tests в этом аудите **UNVERIFIED**.
|
||||
|
||||
### Current vs Historical Document Ledger
|
||||
|
||||
| Документ / группа | Статус | Consumer / disposition |
|
||||
| --- | --- | --- |
|
||||
| `specs/AI_INDEX.md` | current router | Используется AGENTS для точного выбора spec; актуализировать продуктовые входы, SPEC-004 |
|
||||
| `specs/openspec/specs/project-documentation/spec.md` | current normative location, часть содержания устарела | Согласовать conflicting норму, SPEC-001; не объявлять историческим молча |
|
||||
| `specs/openspec/specs/openspec-authority-and-lifecycle/spec.md` | current process spec при явно выбранном SDD | Сохранить approvals и boundaries; отделить проектный preflight от native guarantees, SPEC-002 |
|
||||
| `specs/openspec/changes/stock-screener/**` | active target; approval/acceptance status unknown | Сохранять; checked tasks не дают автоматического права Finalization |
|
||||
| `.../archive/2026-10-09-reconcile-hourly-investment-sync/**` | historical planning/evidence, contradictory completion | Сохранить исходную историю; установить disposition и закрыть ambiguity вне read-only archive, SPEC-002 |
|
||||
| `.../archive/2026-10-09-add-manual-bank-cards/design.md` | historical Design | Сохранять исходные решения и старые paths; «реализация не начата» в preimplementation Design не current-doc bug |
|
||||
| `docs/domains/portfolio/overview.md`, `docs/domains/instruments/stocks.md` | current explanations с drift | Исправить current claims, SPEC-003; не превращать страницу в migration diary |
|
||||
| `docs/architecture/cross-cutting/data-freshness.md`, `docs/domains/portfolio/flows/portfolio-loading.md` | current, более новая модель | Сохранить в качестве входа для semantics; не копировать целиком во все домены |
|
||||
| `docs/adr/017-local-tbank-reads-and-background-sync.md` | historical, superseded части отмечены | KEEP: :3–15 явно направляют к ADR 018/019 и current freshness |
|
||||
| `docs/adr/019-hourly-reads-and-online-instrument-reference.md` | accepted current architectural decision | KEEP: определяет новую границу; не доказывает каждую ветвь runtime |
|
||||
| `docs/adr/020-hourly-sync-verification-evidence.md` | historical synthetic measurement | KEEP полезные условия и ограничения :7–24,36–42. Не переносить цифры в production SLA; отсутствие повторного измерения не делает исторический результат дефектом |
|
||||
| `docs/code-quality-review.md` | dated historical audit, явно разрешённое исключение | KEEP: :5–16 датируют отчёт, :34–36 фиксируют прямое исключение пользователя. Не применять запрет docs-аудитов ретроспективно |
|
||||
| Внешние docs links на fixed commit | revision-bound reference | Не считать broken только из-за старого SHA. Для current route обозначать характер ссылки; remote availability не проверена |
|
||||
|
||||
### Duplication/Drift Register
|
||||
|
||||
| Register ID | Copies / consumers | Вывод | Disposition |
|
||||
| --- | --- | --- | --- |
|
||||
| DR-01 | Canonical `project-documentation` ↔ docs AGENTS/CONTRIBUTING ↔ v2 Docs Impact | Конфликт размещения и обязанности создавать материалы, а не просто похожие слова | SPEC-001 |
|
||||
| DR-02 | Archive `tasks.md` ↔ Completion Evidence ↔ canonical archive blocking requirement | Противоречивое завершение выбранного change; установленный native flow допускает иной lifecycle | SPEC-002 |
|
||||
| DR-03 | Portfolio overview ↔ актуальные flow/freshness ↔ source TTL; stocks body ↔ online appendix ↔ handler | Разные поколения текущего объяснения, практический false expectation | SPEC-003 |
|
||||
| DR-04 | AI_INDEX ↔ canonical product capabilities | Пять выбранных product topics не имеют прямого router entry; все 14 отсутствующих links автоматически дефектами не объявляются | SPEC-004 |
|
||||
| DR-05 | Target screener YAML ↔ backend DTO/generated JSON ↔ frontend JSON/types | Ослаблены два machine-readable ограничения; совпадение publisher/consumer copies не закрывает target review | SPEC-005 |
|
||||
| DR-06 | Backend generated JSON ↔ frontend snapshot ↔ generated TS | Обоснованные producer artifact и consumer derivatives, не конкурирующий editable authority | KEEP |
|
||||
| DR-07 | Immutable archives/ADR ↔ current docs | Историческая копия сохраняет rationale; удаление ради DRY уничтожает полезную историю | KEEP |
|
||||
| DR-08 | `openspec-authority-and-lifecycle/spec.md:380–426` и `specs/README.md:3,14` | Canonical spec содержит одноразовую sequencing/pilot obligation, README сохраняет legacy-transition заметку при отсутствии legacy active metadata в inventory | INVESTIGATE: проверить завершённость перехода у Engineering Owner; не rewrite archive и не менять lifecycle автоматически |
|
||||
|
||||
### Documentation Minimal Baseline
|
||||
|
||||
Минимум определяется потребностью читателя, не числом файлов или требованием «документ на каждый метод».
|
||||
|
||||
| Читатель / задача | Достаточный существующий вход | Что должно остаться |
|
||||
| --- | --- | --- |
|
||||
| Новый участник | `docs/README.md` → `overview.md`, `glossary.md`, architecture Context/Container | Задачи продукта, доменные различия, владельцы данных и внешние системы |
|
||||
| Реализатор use case | `specs/AI_INDEX.md` → точный canonical requirement; при явном SDD — selected change | Проверяемый outcome, ошибки, границы денег/доступа; без обязательного code listing |
|
||||
| Разработчик repo | Локальные AGENTS/README/conventions frontend/backend | Bootstrap, собственный публичный контракт и локальные проверки; независимость от соседнего checkout |
|
||||
| Support / диагностика сумм | `docs/domains/portfolio/how-to/investigate-discrepancy.md`, domain data-reference, freshness | Происхождение цифры, неизвестное vs ноль, source/client state, безопасная диагностика |
|
||||
| Operations / восстановление | `docs/architecture/deployment.md`, Container storage/recovery | Подтверждённая topology, сохранность SQLite/секретов, update/rollback и ограничения |
|
||||
| Изменение API | Backend DTO/OpenAPI + frontend snapshot; при v2 — change-local target | Producer/consumer responsibilities, nullable/required/errors и согласованный переход |
|
||||
| Обоснование решения | `docs/adr/README.md` → конкретный ADR, при локальном решении archived Design | Исторические alternatives/consequences и явная supersession |
|
||||
| Завершение SDD | Выбранные tasks + отдельное observed completion/review evidence | Различие implementation, structural validation и независимой acceptance |
|
||||
|
||||
В просмотренных актуальных тематических входах не обнаружена необходимость добавлять placeholder pages, четыре жанра Diátaxis на каждую область или второй ручной docs index. Не требуется копировать OpenAPI/DTO в Markdown или переносить каждую change sequence в current docs.
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### SPEC-001 — Canonical documentation policy конкурирует с действующими правилами docs
|
||||
|
||||
- **Decision = SIMPLIFY**
|
||||
- **Severity = P2**
|
||||
- **Confidence = High**
|
||||
- **Evidence:** `specs/openspec/specs/project-documentation/spec.md:47–52,77–108,141–150,171–189`; `docs/AGENTS.md:3–14`; `docs/CONTRIBUTING.md:19–26,43–50,81–99`; `specs/openspec/config.yaml:22–26,37–43`; `specs/AI_INDEX.md:33–35`.
|
||||
- **Claim:** одна canonical spec нормативно требует деревья и документы, которые current docs policy исключает или заменяет. Это разрешается только owner decision, а не автоматическим выбором «код/AGENTS важнее spec».
|
||||
- **Current mechanism:** `project-documentation` требует отдельный feature document в `docs/features`, immutable audit snapshots и remediation registry в docs. Docs считает своим содержанием системное объяснение, ADR и тематические страницы, запрещает отчёты исследований и второй архитектурный guide. V2 Docs Impact уже использует новую тематическую модель. Consumer — Finalization/review явно выбранного SDD и contributor, которому нужно обновить docs.
|
||||
- **Impact / actual cost:** следование старой spec создаёт ненужные copies, следование новой policy оставляет формальное расхождение acceptance. **Impact unmeasured**: не измерено, сколько change reviews это задержало.
|
||||
- **Why now:** recent screener и hourly changes уже документированы в `domains/` и `architecture/`; старый обязательный `docs/features` не является фактическим owner.
|
||||
- **Minimum change:** Engineering Owner согласует небольшой delta для `project-documentation`: требовать актуального тематического объяснения, достижимого из existing router, и ADR по значимому решению. Убрать предписание старых paths и обязанность держать аудиты/remediation в docs; сохранить completion evidence в OpenSpec и наблюдаемую проверку. Остальные process specs пересматривать только при подтверждённом аналогичном конфликте.
|
||||
- **Guarantees at risk:** discoverability, сохранность rationale, proof фактической поставки и разграничение planning/current facts. Их сохранить как outcomes; не заменить всё необязательной фразой «при необходимости».
|
||||
- **Counterarguments / trade-offs:** AI_INDEX называет process specs историей и необязательной процедурой обычной разработки; это снижает ежедневный impact. Однако файл остаётся в normative canonical location и содержит SHALL/MUST. Само устаревание исторического audit не finding; `docs/code-quality-review.md` имеет прямое пользовательское исключение.
|
||||
- **Verification or rollback:** сопоставить согласованную новую норму с существующим screener Docs Impact и банковской страницей: пользовательский поток, ограничения, ссылка на owner и current state находятся без второго дерева. Выполнить native spec validation и docs link checks при будущей правке. Откат — revert только нового согласованного delta; history не удалять.
|
||||
- **Effort = S**
|
||||
- **Related finding IDs:** SPEC-003, SPEC-004.
|
||||
|
||||
### SPEC-002 — Архивированное завершение противоречит Tasks и обещанному project gate
|
||||
|
||||
- **Decision = FIX**
|
||||
- **Severity = P2**
|
||||
- **Confidence = High** для противоречия records; причина архивирования неизвестна.
|
||||
- **Evidence:** `specs/openspec/changes/archive/2026-10-09-reconcile-hourly-investment-sync/tasks.md:5–67`; там же `completion-evidence.md:5–18,24–41,52–57`; `specs/openspec/specs/openspec-authority-and-lifecycle/spec.md:148–161`; `specs/README.md:28–31`; `.agents/skills/sdd/openspec-archive-change/SKILL.md:45–70,81–106,207–214`; `$OPENSPEC/dist/core/archive.js:1105–1130`. Git commit `329d66e767f7e89a40b96bb8e5b499f39f67feb6` добавляет этот archive и evidence.
|
||||
- **Claim:** canonical archive содержит 10 незавершённых implementation/docs tasks, но evidence объявляет реализацию и независимое review завершёнными. Native/standard workflow не гарантирует более строгую проектную норму «никакого confirmation override».
|
||||
- **Current mechanism:** Completion Evidence перечисляет backend/frontend/docs commits и successful checks, но оставляет specs delivery как `pending archive commit` и будущую Finalization. Все десять checkbox остались `[ ]`. Project spec запрещает archive с unchecked tasks; native CLI допускает confirmation/`--yes`, стандартный skill также допускает продолжение с warning. Operation guidance явно prompt-level, не enforceable check. Consumer — reviewer/new contributor, использующий archive для terminal status и acceptance trace.
|
||||
- **Impact / actual cost:** нельзя однозначно отличить незавершённую поставку от ошибки bookkeeping по её собственным records; положительный review record не является доказательством закрытия checklist. **Impact unmeasured**. Runtime недопоставка из этого факта не выводится.
|
||||
- **Why now:** это свежий системный v2 change, который задаёт модель чтения всему инвестиционному разделу; его archive используется как подтверждение уже promoted requirements.
|
||||
- **Minimum change:** Finalization Owner устанавливает по delivery commits и исходному review, какие outcomes реально завершены. Для read-only archive сохранить оригинал и добавить явный disposition в текущий локальный вход/новый согласованный record со ссылкой на исторический archive и найденный commit. На будущей Finalization явно проверять отсутствие unchecked implementation tasks и наличие observed review перед стандартным archive; документировать, что это project-controlled preflight, а не обещание native CLI. Не добавлять самописный verifier ради одного record.
|
||||
- **Guarantees at risk:** независимость acceptance, artifact approvals, связь с реальными checks и неизменность архивного evidence. Не ставить `[x]` задним числом только на основании нынешнего code; не выдавать отсутствие Tasks completion за доказанный runtime bug.
|
||||
- **Counterarguments / trade-offs:** commit добавлен общей SDD migration; возможно, checklist не перенесли после уже состоявшегося delivery. Native confirmation соответствует стандартной семантике, это не дефект OpenSpec. Такой контрпример объясняет mechanism, но не делает contradictory evidence самодостаточным.
|
||||
- **Verification or rollback:** read-only сверка десяти outcomes с cited commits и review context, явное заключение Finalization Owner; для следующего change reviewer подтверждает project preflight отдельно от structural status. Rollback нового disposition — revert нового explanatory record, original archive остаётся неизменным. Факт применения override/конкретного archive command — **UNVERIFIED**.
|
||||
- **Effort = M**
|
||||
- **Related finding IDs:** SPEC-003.
|
||||
|
||||
### SPEC-003 — Current domain docs сохраняют прежнюю refresh/reference модель
|
||||
|
||||
- **Decision = FIX**
|
||||
- **Severity = P2**
|
||||
- **Confidence = High**
|
||||
- **Evidence:** `docs/domains/portfolio/overview.md:82–93`; `docs/domains/portfolio/flows/portfolio-loading.md:10–22,34–43`; `docs/architecture/cross-cutting/data-freshness.md:3–12,40–60`; `docs/domains/instruments/stocks.md:149–169,194–199`; `specs/openspec/specs/portfolio-view/spec.md:96–124`; `specs/openspec/specs/online-instrument-information/spec.md:8–17`; `backend/src/integrations/tbank/tbank-source.policy.ts:3–8`; `backend/src/modules/investments/portfolio/application/portfolio.service.ts:55–58,88–108`; `backend/src/modules/investments/instruments/stocks/presentation/stock.controller.ts:112–120`; `backend/src/modules/investments/instruments/stocks/application/stock-catalog.service.ts:59–70`; `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:143–166`.
|
||||
- **Claim:** как минимум две current domain pages дают старые expectations, противоречащие current specs, более новым тематическим docs и изученному handler flow.
|
||||
- **Current mechanism:** Portfolio overview говорит о минутной свежести и refresh зависимостей независимо от свежести. Canonical spec и source TTL используют 60 минут; обычный portfolio GET local/read-only. Stocks body обобщает SQLite reuse metadata/discovery/company и отсутствие ожидания RPC, а его же online appendix сообщает противоположную модель. Обычный stock detail без `sync=observe` передаёт `observe=false` и ждёт upstream `getShare`. Consumer — читатель `docs/README.md:18–19,38`, попадающий сначала на domain explanation, а не на appended correction.
|
||||
- **Impact / actual cost:** contributor/support может ожидать ручной broker refresh или доступность online detail из локального кэша при upstream outage. Новая cross-cutting page не устраняет неверное утверждение в текущей точке входа. **Impact unmeasured**; фактические обращения support не измерены.
|
||||
- **Why now:** модель заменена hourly/online изменениями 9 октября, а страница акций уже обновлялась для скринера 11 октября.
|
||||
- **Minimum change:** адресно заменить старые Portfolio freshness/refresh предложения и разделить на странице акций online detail/company/search от сохраняемых financial resources. Оставить один непротиворечивый current смысл и ссылку на owner freshness page; удалить либо встроить дублирующий appendix после сверки. Отдельно различить внутренние value points и отсутствие публичного history UI, не добавляя новую пользовательскую функцию.
|
||||
- **Guarantees at risk:** fallback сохранённых финансовых данных, local read без personal work, исключения first-load, точность времён и connection access. Не расширять online-only правило на portfolio/quotes/candles или объявлять все SQLite rows актуальным справочником.
|
||||
- **Counterarguments / trade-offs:** stocks body может частично описывать сохранённый `observe` compatibility path. Сейчас scope этого описания не указан и оно сформулировано для обычного чтения; default handler выбирает другой путь. Отсутствие публичного history endpoint корректно. ADR 017 явно historical/superseded и не нуждается в таком cleanup.
|
||||
- **Verification or rollback:** пройти README → Portfolio/Stocks → freshness/HTTP API, сверить утверждения с двумя указанными call chains и existing tests. Для text-only исправления достаточно local Markdown/link checks и semantic review; application build не требуется. Откат — revert current-page diff, без изменения ADR/archives.
|
||||
- **Effort = S**
|
||||
- **Related finding IDs:** SPEC-001, SPEC-002.
|
||||
|
||||
### SPEC-004 — Specs router не покрывает недавние current product capabilities
|
||||
|
||||
- **Decision = FIX**
|
||||
- **Severity = P2**
|
||||
- **Confidence = High**
|
||||
- **Evidence:** `specs/AGENTS.md:8–12`; `specs/AI_INDEX.md:15–35`; `specs/openspec/specs/manual-bank-cards/spec.md:10–31,448–492`; `specs/openspec/specs/hourly-investment-sync/spec.md:8–40`; `specs/openspec/specs/online-instrument-information/spec.md:8–17`; `specs/openspec/specs/portfolio-value-history/spec.md:8–28`; `specs/openspec/specs/tbank-data-sync/spec.md:9–37`; `specs/README.md:14`.
|
||||
- **Claim:** рекомендуемый exact-target router не содержит прямых entries для пяти текущих тем: manual bank cards, hourly investment sync, T-Bank data sync, online instrument information, portfolio value history.
|
||||
- **Current mechanism:** AGENTS направляет читателя в AI_INDEX, который содержит прежний список product specs и общий footer о process/history. Новые capability paths присутствуют в canonical store после migration commit, но в таблице отсутствуют. Consumer — implementer/reviewer банковского сценария либо изменения refresh boundary, которому требуется не загружать всё дерево.
|
||||
- **Impact / actual cost:** повышается вероятность чтения только старого `portfolio-view`/`tbank-connection` или необходимости дополнительного поиска по archive вместо точного current requirement. **Impact unmeasured**; время и agent tokens не замерялись.
|
||||
- **Why now:** карточки и hourly/online модель недавно доставлены; банковская функция является самостоятельной областью без T-Bank и не выводится из старых brokerage topics.
|
||||
- **Minimum change:** добавить пять коротких topic → canonical path entries в существующую таблицу; при необходимости связать их с имеющимися Portfolio/Connection rows. Не делать полный второй индекс всех process/archive файлов. Stock screener не представлять canonical capability до согласованного sync/Finalization.
|
||||
- **Guarantees at risk:** точный выбор behavioral source, отличие historical/active/current, самостоятельность store без соседних repos. Ссылки должны оставаться локальными и lazy.
|
||||
- **Counterarguments / trade-offs:** requirements имеют косвенные textual references и доступны поиском, поэтому это не доказательство полной недостижимости. Девять остальных отсутствующих direct links относятся к process/foundation темам и не требуют автоматического включения. Это локальный router gap, не потребность в глобальном knowledge graph.
|
||||
- **Verification or rollback:** проверить существование targets и маршрут AGENTS → AI_INDEX → нужный requirement; local `git diff --check`. Нужный контекст теперь находится в одном entry, без scan всего store. Откат — revert только добавленных rows.
|
||||
- **Effort = S**
|
||||
- **Related finding IDs:** SPEC-001, SPEC-003.
|
||||
|
||||
### SPEC-005 — Скринер теряет target constraints при публикации OpenAPI
|
||||
|
||||
- **Decision = FIX**
|
||||
- **Severity = P2**
|
||||
- **Confidence = High** для machine-readable drift; runtime consequences не подтверждены.
|
||||
- **Evidence:** `specs/openspec/changes/stock-screener/system-analysis/contracts/stock-screener.openapi.yaml:92–120,174–177`; `specs/openspec/changes/stock-screener/design.md:61–67`; `specs/openspec/changes/stock-screener/tasks.md:25–29`; `backend/src/modules/investments/instruments/stocks/presentation/stock.dto.ts:29–75,85–105`; `frontend/src/shared/api/spec/openapi.json:7495–7527,7570–7573`; `frontend/src/shared/api/generated/schema.d.ts:1285–1314`; `backend/test/openapi-contract.e2e-spec.ts:108–156`; `frontend/scripts/check-openapi.mjs:15–34`.
|
||||
- **Claim:** published/consumer schema не сохраняет два конкретных ограничения change-local target: Criteria `additionalProperties: false` и nullable Item `lot` с `minimum: 1`.
|
||||
- **Current mechanism:** Criteria DTO описывает поля, но не публикует запрет дополнительных properties; Item DTO сообщает integer/nullable без minimum. Generated JSON повторяет эту metadata и совпадает с backend JSON на диске. Frontend types производны от snapshot. Backend parity сравнивает generator output с configured runtime OpenAPI, frontend check — generated types со snapshot; ни одна из прочитанных этих проверок не сравнивает target и published semantics. Consumer — инженер/инструмент, использующий OpenAPI для request validation или анализа допустимых response values.
|
||||
- **Impact / actual cost:** опубликованное описание допускает более широкий shape запроса и более широкий диапазон lot, чем target; producer/consumer agreement не доказывает сохранение согласованного ограничения. **Impact unmeasured**. Фактический запрос с unknown field и фактический ответ с lot 0/negative в этом аудите не воспроизводились.
|
||||
- **Why now:** task 2.2 активного скринера уже отмечена выполненной, хотя delivery plan отдельно требует semantic compatibility до закрытия change. Уточнить расхождение дешевле до Finalization.
|
||||
- **Minimum change:** Backend/Analyst owners согласуют судьбу обоих constraints: восстановить metadata/необходимую boundary guarantee либо явно изменить target с reapproval, если ограничения намеренно неприменимы. Использовать existing HTTP/parity suite для адресной проверки и повторно передать published artifact frontend. Не вводить общую contract registry, второй OpenAPI generator или mandatory cross-checkout CI.
|
||||
- **Guarantees at risk:** request trust-boundary validation, nullable/unknown distinction, page semantics, совместимость consumers. Исправление schema не должно незаметно изменять runtime acceptance или создавать breaking response contract.
|
||||
- **Counterarguments / trade-offs:** runtime может уже обеспечивать строгие значения независимо от Swagger; это не устраняет неполноту published contract, но может ограничить impact. TypeScript не выражает все JSON Schema bounds. Добавленное `logoName` — отдельное additive изменение и само по себе не объявлено дефектом. Понятие semantic compatibility может допускать отдельные расширения, но такое согласование не найдено в прочитанных artifacts.
|
||||
- **Verification or rollback:** на будущей правке сравнить конкретные criteria/item schema nodes target ↔ fresh generated OpenAPI и выполнить targeted HTTP checks для неизвестного property и lot boundary, затем существующие backend parity/frontend type checks. Если runtime менять не нужно, это должно быть подтверждено отдельно. Откат — revert согласованного metadata/target diff и вернуть предыдущий consumer artifact; не исправлять generated TS вручную.
|
||||
- **Effort = S**
|
||||
- **Related finding IDs:** none.
|
||||
|
||||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||||
|
||||
| Decision | Объект | Основание / сохранённая гарантия |
|
||||
| --- | --- | --- |
|
||||
| DELETE | Нет безопасно доказанной группы документов для полного удаления | Не удалять архивы, ADR или полезные explanation pages по размеру либо возрасту |
|
||||
| SIMPLIFY | Старые обязательные `docs/features` и audit/remediation prescriptions | SPEC-001: оставить проверяемый docs outcome и единственный owner вместо восстановления параллельных деревьев |
|
||||
| SIMPLIFY | Повторяющиеся old/new current refresh/reference paragraphs | В рамках SPEC-003 после замены ошибочных утверждений оставить один current смысл и адресные ссылки |
|
||||
| KEEP | 30 canonical specs как store, без общего переписывания | Нормативное behavior и деньги/access acceptance остаются доступны; число файлов не доказательство bloat |
|
||||
| KEEP | Active stock-screener и его target YAML | Не архивировать автоматически по checked tasks; target отдельно от permanent API authority |
|
||||
| KEEP | Legacy schema и metadata исторических changes | Их graph/approvals нельзя поменять переходом default schema |
|
||||
| KEEP | ADR 017 с explicit supersession, ADR 019, historical measurement ADR 020 | Сохранить причины, ограничения измерений и путь к current state |
|
||||
| KEEP | Backend-generated OpenAPI → отдельный frontend snapshot → generated TS | Деривативы имеют разных consumers, гарантируют самостоятельность репозиториев |
|
||||
| KEEP | Точная денежная арифметика, DB transactions/CAS, replay/idempotency и connection fencing | Документационное упрощение не удаляет механизмы денег, публикации и доступа |
|
||||
| KEEP | Независимый semantic/QA review и explicit approval | Structural success и archive location не дают acceptance proof |
|
||||
| INVESTIGATE | Disposable transition/pilot constraints в canonical process spec | DR-08: owner должен отделить завершённый rollout от enduring invariant; сейчас history не менять |
|
||||
| INVESTIGATE | Конкретная причина unchecked reconciliation archive | SPEC-002: выяснить по original delivery/review, не по догадке о запуске CLI |
|
||||
| FIX | Current router, docs drift, completion disposition, target constraints | SPEC-002–005; точечные изменения вместо нового tooling |
|
||||
|
||||
### Стандартные OpenSpec artifacts/skills — оставить без изменения
|
||||
|
||||
Этот аудит не предлагает менять установленный OpenSpec CLI, его встроенные schemas/templates или стандартные skills:
|
||||
|
||||
- `openspec-new-change`, `openspec-propose`, `openspec-continue-change`, `openspec-ff-change`;
|
||||
- `openspec-apply-change`, `openspec-update-change`, `openspec-verify-change`;
|
||||
- `openspec-sync-specs`, `openspec-archive-change`, `openspec-bulk-archive-change`;
|
||||
- `openspec-explore`, `openspec-onboard`.
|
||||
|
||||
Inventory paths: `.agents/skills/sdd/openspec-*/SKILL.md`. Подробно прочитан archive skill только для проверки lifecycle semantics; пошаговая стоимость skill loading здесь не исследуется.
|
||||
|
||||
Сохранить стандартные `proposal.md`, delta `spec.md`, `design.md`, `tasks.md`, `.openspec.yaml`, операции `ADDED/MODIFIED/REMOVED/RENAMED`, canonical `Purpose/Requirements/Scenario`, native sync/archive и history. Project-owned v2 `system-analysis`, `qa-analysis`, `docs-impact`, target contracts и выбранные sequences также сохраняются по schema. Обнаруженный конфликт строжайшего project gate решается project-level disposition/preflight, а не правкой стандартного skill.
|
||||
|
||||
## 6. Cost & benefit
|
||||
|
||||
| Finding / решение | Класс ожидаемой пользы | Фактическое измерение | Обязательная граница при изменении |
|
||||
| --- | --- | --- | --- |
|
||||
| SPEC-001 | Maintenance, cognitive load, предотвращение лишних документов | not measured | Сохранить полезное объяснение, rationale, navigation и проверяемый completion |
|
||||
| SPEC-002 | Надёжность evidence, меньше повторного восстановления delivery state | not measured | Не подменить independent acceptance, не переписать original archive |
|
||||
| SPEC-003 | Cognitive load, меньше неверных assumptions о freshness/outage recovery | not measured | Read locality, fallback, online/local distinction, правильные времена |
|
||||
| SPEC-004 | Navigation, agent tokens и человеческое время адресного чтения | not measured | Один существующий индекс; не обязательное чтение всего store |
|
||||
| SPEC-005 | Стабильность публичного контракта, меньше будущего integration rework | not measured | Совместимость target/published/consumer и trust-boundary validation |
|
||||
| KEEP artifact copies/archives | Сохранение contract autonomy и historical rationale | not measured | Не превращать экономию файлов в потерю доказательств или cross-repo CI dependency |
|
||||
|
||||
Runtime и CI minutes savings этим аудитом не установлены. Для text-only/router исправлений application build не требуется; это граница проверок, а не измеренное ускорение. Новые dependencies не предлагаются, удаление dependencies не обосновано.
|
||||
|
||||
### Ponytail-audit: компактный complexity результат
|
||||
|
||||
- `shrink:` заменить старые обязательные docs trees единым проверяемым outcome в existing thematic owner. `[specs/openspec/specs/project-documentation/spec.md]` — SPEC-001.
|
||||
- `shrink:` убрать конкурирующий old/new current refresh/reference пересказ после semantic correction; оставить current explanation и ссылку. `[docs/domains/portfolio/overview.md; docs/domains/instruments/stocks.md]` — SPEC-003.
|
||||
- `yagni:` не добавлять новый manual index, schema registry, docs generator или собственный archive verifier; existing router и адресная проверка покрывают выявленный scope.
|
||||
|
||||
**net:** сокращение строк не измерено; 0 dependencies определены как кандидаты на удаление. Нельзя честно назвать `-N lines` до согласованного конкретного diff.
|
||||
|
||||
## 7. Top actions
|
||||
|
||||
### Quick wins
|
||||
|
||||
1. **SPEC-003:** исправить current Portfolio/Stocks refresh/reference объяснение, сохранив explicit разделение online reference и local financial read. Value высокое, риск низкий, effort S.
|
||||
2. **SPEC-004:** добавить пять точных product-topic entries в existing AI_INDEX. Value высокое для новых сценариев, риск низкий, effort S.
|
||||
3. **SPEC-001:** согласовать и сократить устаревшие documentation-path prescriptions до тематического docs outcome. Value среднее/высокое, риск локальный, effort S; это изменение нормы, поэтому нужен Engineering Owner.
|
||||
|
||||
### Требуют подтверждения owner / contract / delivery
|
||||
|
||||
1. **SPEC-002:** установить фактический disposition reconciliation archive по commits и original independent review; сохранить original records, явно обозначить расхождение. Value высокое для доверия к completion, effort M.
|
||||
2. **SPEC-005:** перед Finalization скринера проверить и согласовать два target constraints; исправить metadata либо получить target reapproval и подтвердить адресными checks. Value среднее/высокое, effort S.
|
||||
3. **SPEC-002:** для следующей Finalization явно отделить project preflight от native structural/archive success в существующем процессном входе. Не обещать недоказанную машинную блокировку и не менять стандартные skills.
|
||||
|
||||
Новый ADR не обязателен для редакционных правок или router entries. ADR/process decision потребуется только если owner решит изменить enduring archive/authority semantics; это отдельное согласованное решение, не автоматический результат аудита.
|
||||
|
||||
## 8. Cross-audit signals
|
||||
|
||||
- **Audit 08 — Test Quality:** сила late-response proof в `frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–237`; здесь использован только как ограничение test evidence, без повторного расследования.
|
||||
- **Audit 07 — API/Data Flow:** фактический hourly/read runtime и source failure branches в `backend/src/modules/investments/tbank-sync/`, `operations/`, `upcoming-events/`; documentation mismatch не объявлен доказательством нового runtime дефекта.
|
||||
- **Audit 14 — AI workflow:** частота загрузки большого schema/context и стандартных OpenSpec skills; `specs/openspec/schemas/wealth-ledger-sdd-v2/schema.yaml`, `.agents/skills/sdd/`. Token-cost исследование в audit 15 не выполнялось.
|
||||
- **Audit 11 — Validation/CI:** реальные merge/branch-protection settings и исполнение required acceptance gates; наличие документации и workflow не подтверждает remote enforcement. Сетевая проверка не выполнялась.
|
||||
|
||||
## 9. Open questions / verification plan
|
||||
|
||||
| Вопрос, меняющий решение | Статус | Самая дешёвая проверка |
|
||||
| --- | --- | --- |
|
||||
| Завершены ли все десять outcomes reconciliation или только перенос checklist неполон? | UNVERIFIED | Сопоставить outcomes с cited delivery commits и оригинальным review; Engineering/Finalization Owner даёт явный disposition. Без этого нельзя поставить checkbox задним числом |
|
||||
| Был ли использован confirmation override/native command или archive перенесли при migration иначе? | UNVERIFIED | Проверить существующий finalization context/PR, без повторного archive или новых инструментов. Это меняет причину SPEC-002, но не противоречие records |
|
||||
| Какой документационный scope теперь нормативен при явном SDD? | Требует owner decision | Engineering Owner сопоставляет SPEC-001 со screener Docs Impact и текущим CONTRIBUTING; согласует минимальный delta |
|
||||
| Являются ли дополнительные properties и lot minimum намеренно изменённым target contract? | UNVERIFIED | Backend/System Analyst проверяют два конкретных schema nodes и approval context; выбрать metadata correction либо reapproval |
|
||||
| Runtime действительно отвергает неизвестное screener field и не выдаёт lot вне target? | UNVERIFIED | При работе над SPEC-005 — адресный HTTP boundary check и управляемый источник; не реальный T-Invest token |
|
||||
| Backend artifact свежий относительно текущих handlers, generated TS полностью соответствует snapshot? | UNVERIFIED для нового запуска | При contract fix: после локального `nvm use --silent` existing `openapi:generate`/`test:openapi-contract`, затем frontend `openapi:check`. Эти команды здесь не запускались, генерация нарушила бы read-only режим |
|
||||
| Legacy-transition и одноразовый UI pilot полностью завершены? | UNVERIFIED | Проверить disposition соответствующих archived changes у Engineering Owner; если закрыты, отдельный approved delta удаляет лишь завершённые transition obligations |
|
||||
| Доступны ли внешние fixed-commit URLs? | UNVERIFIED | При необходимости проверить только используемые URL в обычной сетевой среде. Их старый revision сам по себе не broken link |
|
||||
|
||||
### Проверка самого результата
|
||||
|
||||
- До записи четыре repo и root имели пустой `git status --porcelain=v1`; `git diff --check` завершился успешно.
|
||||
- Markdown отчёта проверен существующим локальным `markdownlint` из docs после активации `.nvmrc`; проверяется только итоговый файл, без установки и auto-fix. Первый запуск выявил три `MD029` из-за продолжения нумерации после подзаголовка; нумерация исправлена, повторный запуск — **PASS, exit 0**.
|
||||
- После записи отдельно проверены HEAD/status/diff четырёх repos и root. Единственная созданная запись — этот ignored root report; приложение, specs store и docs не изменены.
|
||||
- Semantic/Boy Scout review отчёта: у каждого finding есть source/consumer, механизм, контрпример, minimum change и сохранённые гарантии; current/historical/active не смешаны, исторические измерения не представлены как новые, отсутствие runtime proof помечено. Report не создаёт нового process authority.
|
||||
- Проверка Markdown не подтверждает работу приложения, рендер существующих Mermaid-диаграмм или успешность перечисленных application tests.
|
||||
|
||||
Команда проверки отчёта, working directory `docs/`:
|
||||
|
||||
```bash
|
||||
source "$HOME/.nvm/nvm.sh" && nvm use --silent && ./node_modules/.bin/markdownlint --config .markdownlint.json -- "/Users/ksv741/Project/vibe/WealthLedger/audit-results/15-sdd-specs-and-documentation.md"
|
||||
```
|
||||
|
||||
Итоговые repo checks: `git rev-parse HEAD && git status --porcelain=v1 && git diff --check` отдельно в каждом checkout — исходные SHA, пустой status, exit 0. `git check-ignore audit-results/15-sdd-specs-and-documentation.md` подтвердил, что отчёт находится в ignored root area.
|
||||
|
||||
## 10. Машинно-считываемая таблица для консолидации
|
||||
|
||||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| SPEC-001 | SIMPLIFY | P2 | High | specs,docs | specs/openspec/specs/project-documentation/spec.md;docs/CONTRIBUTING.md;docs/AGENTS.md | Conflicting normative documentation ownership encourages duplicate trees and ambiguous acceptance | S | SPEC-003,SPEC-004 |
|
||||
| SPEC-002 | FIX | P2 | High | specs | specs/openspec/changes/archive/2026-10-09-reconcile-hourly-investment-sync/tasks.md;specs/openspec/changes/archive/2026-10-09-reconcile-hourly-investment-sync/completion-evidence.md;specs/openspec/specs/openspec-authority-and-lifecycle/spec.md | Archived completion contradicts 10 unchecked tasks and promised project archive gate | M | SPEC-003 |
|
||||
| SPEC-003 | FIX | P2 | High | docs | docs/domains/portfolio/overview.md;docs/domains/instruments/stocks.md | Current domain explanations retain obsolete freshness and reference-refresh assumptions | S | SPEC-001,SPEC-002 |
|
||||
| SPEC-004 | FIX | P2 | High | specs | specs/AI_INDEX.md | Current product capabilities lack direct entries in the required exact-target router | S | SPEC-001,SPEC-003 |
|
||||
| SPEC-005 | FIX | P2 | High | backend,specs,frontend | backend/src/modules/investments/instruments/stocks/presentation/stock.dto.ts;specs/openspec/changes/stock-screener/system-analysis/contracts/stock-screener.openapi.yaml;frontend/src/shared/api/spec/openapi.json | Published screener schema omits two target constraints despite matching producer and consumer copies | S | none |
|
||||
@@ -0,0 +1,765 @@
|
||||
# WealthLedger — Complexity, Redundancy & Engineering Overhead Audit
|
||||
|
||||
Дата: 11 октября 2026 года.
|
||||
|
||||
Метод: `ponytail-audit`, статический анализ owners, consumers, проверок и последствий удаления.
|
||||
|
||||
## Основание и ограничения
|
||||
|
||||
Изучены root, `frontend`, `backend`, `docs`, `specs` и обнаруженный отдельный репозиторий `deploy`. Первый отчёт `technical-audit-2026-10-11.md` прочитан. Его функциональные дефекты и уже перечисленные simplification opportunities не включены в новые findings.
|
||||
|
||||
Основные checkout совпадают с первым аудитом:
|
||||
|
||||
| Репозиторий | Commit |
|
||||
|---|---|
|
||||
| frontend | `c882120` |
|
||||
| backend | `0ad84ff` |
|
||||
| docs | `e6fd4f6` |
|
||||
| specs | `fcdc805` |
|
||||
|
||||
Выполнены чтение, поиск, Git inspection и подсчёты через `rg`. Единственная дополнительная проверка инструментом:
|
||||
|
||||
```sh
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use --silent
|
||||
node node_modules/openapi-typescript/bin/cli.js \
|
||||
src/shared/api/spec/openapi.json \
|
||||
-o src/shared/api/generated/schema.d.ts \
|
||||
--check
|
||||
```
|
||||
|
||||
**Результат: exit 0, установленный `openapi-typescript 7.13.0`.** Проверка не записывает output: сравнение и выход происходят до `mkdirSync`/`writeFileSync` — `frontend/node_modules/openapi-typescript/bin/cli.js:125–134,280–291`.
|
||||
|
||||
Build, полные verify, тестовые наборы, установки, Docker и deployment не запускались. Финальные `git status --short` всех шести обследованных рабочих деревьев не показали изменений.
|
||||
|
||||
Время, стоимость CI, bundle size и расход токенов не измерялись.
|
||||
|
||||
# A. Executive Summary
|
||||
|
||||
## Главные источники избыточности
|
||||
|
||||
1. **Обязательный полный локальный workflow для любого code change.**
|
||||
|
||||
Frontend и backend требуют `npm ci` и полный `verify` перед завершением, независимо от изменённой области. Дополнительные conventions отдельно направляют к typecheck/lint/risk checks. Это расширяет небольшую задачу до полного bootstrap и проверки всего приложения.
|
||||
|
||||
Evidence: `frontend/.ai/conventions/engineering.md:58–66`, `backend/.ai/conventions/engineering.md:58–66`, `frontend/.ai/conventions/ui.md:165–170`.
|
||||
|
||||
2. **Повторная работа внутри одной границы проверки.**
|
||||
- Frontend `verify` вызывает TypeScript дважды.
|
||||
- Backend отдельно typecheck’ит application, хотя test typecheck включает те же application sources.
|
||||
- Backend CI собирает приложение в `verify`, затем повторно в `test:e2e`.
|
||||
- SQLite runner regression повторяет весь functional suite пять раз ради свойств самого runner.
|
||||
|
||||
Evidence: `frontend/package.json:26`, `backend/package.json:22–34`, `backend/tsconfig.typecheck.test.json:7–8`, `backend/.gitea/workflows/verify.yml:84–89`, `backend/test/sqlite-regression.cjs:20–22,77–91`.
|
||||
|
||||
3. **Самописная проверка, уже реализованная установленным инструментом.**
|
||||
|
||||
`check-openapi.mjs` — 45 строк temp-directory/subprocess/cleanup orchestration для возможности `openapi-typescript --check`. Это наиболее уверенный tooling-cut с сохранением той же гарантии.
|
||||
|
||||
4. **Production API, поддерживаемые тестовыми consumers.**
|
||||
- `useAuthenticatedQueries`: production callers отсутствуют.
|
||||
- `InstrumentMetadataService.read`: используется metadata persistence suite; production использует `request`/`savedMany`.
|
||||
|
||||
Evidence: `frontend/src/shared/api/authenticated-query.ts:15–32,61`, `frontend/src/shared/api/authenticated-query.test.tsx:9–23`, `backend/src/integrations/tbank/instrument-metadata.service.ts:41–63`, `backend/test/tbank-metadata-persistence.e2e-spec.ts:87–382`.
|
||||
|
||||
5. **Тесты иногда обходят дешёвую существующую boundary.**
|
||||
|
||||
Чистые преобразования проверяются через fake Prisma transactions или background orchestration; page tests поддерживают fake query-result shapes параллельно существующей MSW boundary.
|
||||
|
||||
6. **Противоречивая процессная authority.**
|
||||
|
||||
Локальные инструкции задают opt-in OpenSpec и запрещают audit artifacts в docs. Действующие process specs требуют registry, snapshots и canonical workflow для audit-derived работы. Это создаёт дополнительные artifacts и решения о том, какую инструкцию исполнять.
|
||||
|
||||
Evidence: `specs/AGENTS.md:3–6`, `docs/AGENTS.md:12–14`, `specs/openspec/specs/project-foundation/spec.md:9–19`, `specs/openspec/specs/audit-remediation-workflow/spec.md:9–49`.
|
||||
|
||||
## Общая оценка
|
||||
|
||||
**Масштабный архитектурный рефакторинг не нужен.** Не подтверждена потребность вводить новые interfaces, generic workers, fixture frameworks, архитектурные validators или quality tools.
|
||||
|
||||
Самые полезные изменения сокращают **число путей, запусков и обязательных artifacts**, сохраняя существующие owners и реальные behavioral proofs.
|
||||
|
||||
# B. Complexity Inventory
|
||||
|
||||
## B1. Количественные показатели
|
||||
|
||||
| Метрика | Результат | Метод и ограничения |
|
||||
|---|---:|---|
|
||||
| Package script entries | **67** | Root 3; frontend 25; backend 27; docs 5; specs 5; UI/icons workspace builds 2. Это entrypoints и aliases, не 67 самописных программ |
|
||||
| Custom script/adapter/harness/plugin files | **21** | Frontend 6; backend 10; docs 2; specs 1; deploy 2. Git hooks и декларативные configs отдельно |
|
||||
| Custom Git hooks | **3** | Frontend pre-commit/commit-msg; specs commit-msg. Generated Husky files и Git samples исключены |
|
||||
| Custom release hook declarations | **4** | Frontend/backend/docs/specs `after:git:init`; root такого helper не имеет |
|
||||
| Frontend `verify` command stages | **12** | Разделение команды на последовательные стадии; вложенные workspace builds отдельно не разворачиваются |
|
||||
| Backend `verify` command stages | **6** | Lint, lint-config tests, два typecheck, unit coverage, build |
|
||||
| Operational verification families | **29** | Группы в разделе C; не количество отдельных ESLint rules или test assertions |
|
||||
| Участки повторной/малоинформативной проверки | **7** | Frontend TS; backend TS subset; backend rebuild; full-suite runner repetition; preset-parity meta-tests; docs/specs clean-checkout diff checks |
|
||||
| Test files frontend/backend | **63 / 59** | Backend: 39 colocated specs + 20 integration suites. Browser files входят в frontend 63 |
|
||||
| Test files вместе с deploy harness | **123** | 122 TS/TSX test files + `deploy/tests/deploy.sh`; CJS инфраструктурные проверки здесь отдельно |
|
||||
| Production TS/TSX | **34 465 строк** | Frontend 16 967; backend 17 498. Generated, stories, fixtures/test support исключены |
|
||||
| Test TS/TSX | **23 679 строк** | Frontend 11 537; backend 12 142 |
|
||||
| Test/production ratio | **≈ 0,69** | Физические строки, включая comments/blank lines. Не критерий качества архитектуры |
|
||||
| Frontend stories / support | **18 файлов / 6 файлов** | 1 855 / 263 строки соответственно; не включены в test LOC |
|
||||
| Классифицированные factory/polling definitions | **не менее 18** | Frontend 3 общие factories; backend 4 service factories, 4 deferred helpers, 7 polling helpers. Локальные render/helpers не полностью включены |
|
||||
| Frontend module mocks | **4 в 3 файлах** | `vi.mock`; не учитывает spies и MSW handlers |
|
||||
| Backend module mocks | **18 в 12 unit files** | `jest.mock`; сам count не является finding |
|
||||
| Snapshot assertions | **0 найдено** | Поиск `toMatchSnapshot`, `toMatchInlineSnapshot`, `toMatchFileSnapshot`, `__snapshots__` в обследованных test sources |
|
||||
| Backend runtime SHA/HMAC mechanisms | **8** | 7 `createHash` emission sites + один signed-cursor mechanism с encode/decode HMAC. Password bcrypt отдельно |
|
||||
| Gitleaks archive checksum steps | **4** | Одна download-integrity policy в четырёх независимых repos |
|
||||
| OCI digest publication paths | **2** | Frontend/backend image workflows |
|
||||
| Подтверждённые точки удаления | **18** | Перечень в G; считаются удаляемые элементы/стадии, не файлы |
|
||||
| Обследованные single-production-consumer abstractions | **6 примеров** | RouteTransition, InstrumentSearch, useInstrumentSearch, metadata repository `work`, `queueSnapshot`, `TBANK_ACCESS`. Это выборка, не полный census |
|
||||
|
||||
LOC и количества используются для описания масштаба. Они **не доказывают** избыточность.
|
||||
|
||||
## B2. Все custom scripts и исполняемые adapters
|
||||
|
||||
| Файл | Назначение и consumers; автоматизация | Пересечение / последствия удаления / решение |
|
||||
|---|---|---|
|
||||
| `frontend/scripts/check-openapi.mjs:1–45` | Freshness generated types; вызывается `openapi:check` и `verify` | Полностью покрывается native `--check`. **Удалить реализацию, сохранить проверку** |
|
||||
| `frontend/scripts/check-release.cjs:1–24` | Manifest version ↔ reachable stable tag; release-it hook | Не эквивалентен `requireCommits`. **KEEP**: предотвращает повторную подготовку версии |
|
||||
| `frontend/scripts/test-eslint.mjs:23–395` | Config/rule/fixer proofs; автоматически из `test:lint`/`verify` | Смешивает проектные checks и exhaustive preset parity. **SIMPLIFY**, сохранять probes реальных exceptions/custom fixers |
|
||||
| `frontend/scripts/test-pre-commit.mjs:17–79` | Staged-index scanning, redaction, fail-closed; security CI и ручная команда | Full-history scan не доказывает index behavior. **KEEP** |
|
||||
| `frontend/scripts/check-image-publication.cjs:15–88` | Модель retry после post-push failure, exact digest/revision/smoke | Автоматических callers не найдено. Не считать dead только из-за этого: самостоятельный workflow proof. **KEEP как targeted check**, не обязательный gate каждой правки |
|
||||
| `frontend/eslint-rules.mjs:1–178` | Два custom ESLint rules; загружается lint config | Часть правил — formatting/обязательная форма кода. **SIMPLIFY**, не заменять новым analyzer |
|
||||
| `backend/scripts/check-release.cjs:1–11` | Version/tag guard; release-it hook | Штатный release guard не покрывает эту policy. **KEEP** |
|
||||
| `backend/scripts/generate-openapi.cjs:1–41` | Swagger generation из built app без deployment config | Package command и contract integration suite. TypeScript сам контракт не генерирует. **KEEP** |
|
||||
| `backend/test/sqlite.cjs:1–95` | Per-suite temporary DB, migrations, subprocesses, signal cleanup | Все SQLite entrypoints. Обычный Jest не гарантирует DB isolation. **KEEP** |
|
||||
| `backend/test/sqlite-regression.cjs:1–96` | Runner isolation/failure/signal proof; ручной README entrypoint | Пять полных успешных suite runs избыточны для runner properties. **SIMPLIFY область прогонов** |
|
||||
| `backend/test/e2e-env.cjs:1–17` | Отказ от не-изолированной DB, synthetic credentials | Jest setup и crash fixture. **KEEP**: защита данных developer |
|
||||
| `backend/test/fixtures/tbank-sync-crash.cjs:1–86` | Управляемый kill процесса на durable boundaries | Consumer — operations persistence suite. In-process mocks не заменяют crash proof. **KEEP** |
|
||||
| `backend/test/eslint-config.cjs:9–146` | Profile scope, generated ignores, import exceptions | Автоматически `test:lint`. **SIMPLIFY**: project scope/exception checks полезны; поведение каждой library rule не нужно доказывать заново |
|
||||
| `backend/test/runtime-check.cjs:1–8` | Проверяет строку start command, наличие dist и require generated client | Runtime callers не найдены; config test использует имя как fixture path. Real image smoke сильнее. **DELETE** |
|
||||
| `backend/test/container-smoke.cjs:1–77` | Native modules/assets, migration, non-root/read-only runtime, auth/restart | Image publication и manual package entrypoint. Host build это не доказывает. **KEEP** |
|
||||
| `backend/test/bank-cards-durability.cjs:1–219` | Historical build → migration → restart → rollback → restore | Risk-triggered manual entrypoint. Fresh DB suites не заменяют historical compatibility. **KEEP** |
|
||||
| `docs/scripts/check-release.cjs:1–10` | Clean status + version/tag guard | Release hook. Porcelain видит untracked files; native clean guard не полностью эквивалентен. **KEEP** |
|
||||
| `docs/.markdownlint-rule-relative-links.cjs:1` | Adapter `.default` установленного link rule | Оба markdownlint запуска. Не собственный validator. **KEEP** |
|
||||
| `specs/scripts/check-release.cjs:1–10` | Clean status + version/tag guard | Release hook. **KEEP** |
|
||||
| `deploy/deploy.sh:1–192` | Tagged snapshot, preflight, lock/pending, backup, migration/readiness | Ручной запуск, затем self-handoff к tagged script. **KEEP** |
|
||||
| `deploy/tests/deploy.sh:1–251` | Real Git/files/locks + Docker double; optional bind-restore proof | Ручной entrypoint. **KEEP**: orchestration и bind semantics не доказываются application tests |
|
||||
|
||||
**Не установлена необходимость централизовать десятистрочные release helpers между независимыми repos.** Общий tooling package добавит dependency boundary и release ownership ради небольшого копирования.
|
||||
|
||||
## B3. Package scripts: роль и оправданность
|
||||
|
||||
Определения: `package.json:8–12`, `frontend/package.json:9–34`, `backend/package.json:11–38`, `docs/package.json:8–14`, `specs/package.json:8–14`.
|
||||
|
||||
| Группа entrypoints | Consumers и оценка |
|
||||
|---|---|
|
||||
| Root `release:preview`, `release:prepare`, `commitlint` | Release/operator и commits CI. Штатные CLI wrappers; оставить |
|
||||
| Frontend `dev`, `dev:catalog`, `build:catalog`, `preview:catalog`, `preview` | Developer/catalog lifecycle. Разные outputs; не дублируют одну проверку |
|
||||
| Frontend `build`, `build:packages`, `typecheck` | Application/workspace compilation. Packages нужны из-за dist-based exports; проблема — частота повторного запуска |
|
||||
| Frontend `lint`, `lint:eslint`, `lint:fix` | Alias, check, explicit edit. `lint` alias сам по себе не требует удаления |
|
||||
| Frontend `test`, `test:run`, `test:coverage`, `test:lint` | Watch, one-shot, coverage, tooling tests. Различимые режимы; уменьшать обязательность, не механически удалять entrypoints |
|
||||
| Frontend `openapi:generate`, `openapi:check` | Explicit generation и read-only freshness. Оставить оба; check реализовать native CLI |
|
||||
| Frontend `knip`, `deps`, `deps:storybook` | Dead usage, production boundaries, transitive catalog isolation — разные свойства |
|
||||
| Frontend `verify`, `prepare`, release pair, `commitlint` | Aggregate gate, Husky bootstrap, release и commit policy. `verify` упростить |
|
||||
| Backend `start`, `start:dev`, `start:debug`, `start:prod` | Различные runtime/debug modes; production также задаётся Docker |
|
||||
| Backend `build`, `openapi:generate` | Prisma/Nest output и public artifact; оставить standalone completeness |
|
||||
| Backend `format`, `lint`, `lint:check`, `lint:fix` | Aliases и явный check/edit. Небольшая стоимость, нет причины строить новые wrappers |
|
||||
| Backend `typecheck:app`, `typecheck:test` | Оставить targeted entrypoints; в полном gate достаточно test superset |
|
||||
| Backend `test`, `test:watch`, `test:cov`, `test:debug` | Runner modes; текущая необходимость debug не опровергнута отсутствием CI caller |
|
||||
| Backend `test:e2e`, `test:sqlite`, `test:performance`, `test:openapi-contract` | Full functional, auth/cards subset, отдельный benchmark, отдельный contract proof. Сокращать repeated build при orchestration, не смешивать наборы |
|
||||
| Backend `test:container`, `test:durability` | Image/historical-data boundaries; risk-triggered |
|
||||
| Backend `verify`, `test:lint`, release pair, `commitlint` | Aggregate/tooling/release policy; упростить aggregate |
|
||||
| Docs `lint`, `verify`, release pair, `commitlint` | Содержательный Markdown gate. Два lint запуска обрабатывают разные наборы с разными changelog rules |
|
||||
| Specs `verify`, release pair, `commitlint`, `prepare` | `verify` полезен только как local diff check в нынешнем виде; `prepare` незаметно меняет hooksPath |
|
||||
| UI/icons `build` | Vite bundle + TypeScript declarations для двух реально используемых packages |
|
||||
|
||||
## B4. Hash, SHA, freshness и integrity
|
||||
|
||||
| Группа / механизм | Реальный failure mode | Альтернативные защиты и риск удаления | Решение |
|
||||
|---|---|---|---|
|
||||
| **Business idempotency:** bank intent fingerprint | Повтор запроса после lost response; тот же ID с другим содержимым | Unique ID предотвращает вторую запись, но не различает повтор и конфликтующий intent. `backend/src/modules/banking/bank-cards/application/bank-card-input.ts:74–81`, `bank-cards.service.ts:76–81`, `bank-operations.service.ts:216–223` | **KEEP** |
|
||||
| **Security:** refresh-token SHA и password bcrypt | Чтение БД не должно раскрывать reusable token/password; rotation CAS | CAS не заменяет безопасное хранение. `backend/src/modules/auth/application/auth.service.ts:52,110–133,180,204–205` | **KEEP** |
|
||||
| **Cursor integrity:** HMAC + revision/filter binding | Подмена cursor или применение к изменённому dataset/filter | Ownership проверяет доступ, но не подписанность и согласованность cursor. `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:347–388`, `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:128–143` | **KEEP** |
|
||||
| **Work identity:** hash canonical operations intervals | Разные диапазоны не должны случайно объединяться; одинаковые intents coalesce | Возможна строковая canonical identity, но существующий механизм короткий и связан с durable keys. `backend/src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:254–269` | **KEEP**, нет доказанного выигрыша от замены |
|
||||
| **Resource identity/revisions:** accounts, candles, operations | Согласованность metadata и identity текущей read-model | Это public/runtime metadata, не development gate. `backend/src/modules/investments/brokerage-accounts/brokerage-accounts.service.ts:42–54`, `backend/src/modules/investments/instruments/infrastructure/candle.repository.ts:41–52`, `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:238–239` | **KEEP до установленного contract-safe cut** |
|
||||
| **Freshness metadata/TTL** | Отсутствие данных, stale и confirmed completeness нельзя смешивать | Coverage/revisions защищают другие свойства. `backend/src/modules/investments/investment-resource.mapper.ts:31–64,78–88`, `backend/src/integrations/tbank/tbank-source.policy.ts:1–21` | **KEEP**; удалить только неиспользуемый `candleTail` key |
|
||||
| **Data integrity:** SQLite integrity/FK checks | Повреждение/невалидные связи после migration/restore | Unit mocks и successful startup недостаточны. `backend/test/bank-cards-durability.cjs:109–118,190–205`, `backend/test/container-smoke.cjs:58–64` | **KEEP на соответствующих boundaries** |
|
||||
| **Generated artifacts:** local OpenAPI → types | Committed types расходятся с предоставленным snapshot | TS не сравнивает types с JSON. Native `--check` покрывает custom script | **SIMPLIFY implementation, KEEP guarantee** |
|
||||
| **Runtime OpenAPI generator parity** | Generator отличается от configured app schema | Frontend freshness этого не доказывает. `backend/test/openapi-contract.e2e-spec.ts:108–156` | **KEEP** |
|
||||
| **Download integrity:** Gitleaks tarball SHA-256 | Исполнение не тех bytes скачанного binary | HTTPS/version URL не эквивалентны pinned checksum. Frontend/backend verify `52–58`, docs/specs security `24–30` | **KEEP** |
|
||||
| **Image content identity:** OCI digest/revision | Smoke проверяет другой image, tag изменён, retry скрывает несовпадение | Host tests не доказывают bytes опубликованного image. `frontend/.gitea/workflows/image.yml:46–62`, `backend/.gitea/workflows/image.yml:42–55` | **KEEP** |
|
||||
| **Process identity:** release manifest/tag, deployment Git commit | Повторная подготовка версии; deployment не из выбранного tagged snapshot | Native release-it покрывает лишь часть; рабочий checkout не immutable input. `deploy/deploy.sh:26–39` | **KEEP** |
|
||||
| **Process visual SHA tables** | Approved export изменён отдельно от approval input | Git commit binding может покрывать Git-only delivery; отдельно передаваемые exports — другой риск. `specs/openspec/schemas/wealth-ledger-sdd-v2/schema.yaml:322–330`, `specs/openspec/schemas/wealth-ledger-sdd-v2/templates/design.md:38–61` | **Условный SIMPLIFY**, не безусловное удаление |
|
||||
| **npm lockfile integrity** | Package bytes не соответствуют lockfile | Стандартный механизм npm, не custom framework | **KEEP** |
|
||||
|
||||
**Самописных self-hash/fingerprint validators для planning Markdown или hash-gated reuse CI результатов не обнаружено.** Требования в prose не равны действующей автоматизации.
|
||||
|
||||
# C. Redundancy Matrix
|
||||
|
||||
## Фактическая цепочка
|
||||
|
||||
### Frontend
|
||||
|
||||
```text
|
||||
Developer / AI:
|
||||
npm ci
|
||||
targeted typecheck/lint/tests
|
||||
full verify
|
||||
|
||||
pre-commit:
|
||||
staged Gitleaks → lint-staged ESLint → deps → build:packages
|
||||
|
||||
CI:
|
||||
commitlint → history Gitleaks / audit / hook proof
|
||||
→ full verify
|
||||
|
||||
full verify:
|
||||
packages → OpenAPI freshness → TS → ESLint → lint-config tests
|
||||
→ Vitest coverage → Knip → production graph → story graph
|
||||
→ TS again → application build → Storybook build
|
||||
```
|
||||
|
||||
Источники: `frontend/.ai/conventions/engineering.md:58–66`, `frontend/.husky/pre-commit:1–3`, `frontend/.gitea/workflows/verify.yml:17–100`, `frontend/package.json:26`.
|
||||
|
||||
### Backend
|
||||
|
||||
```text
|
||||
Developer / AI:
|
||||
npm ci → targeted checks → full verify → risk-specific suites
|
||||
|
||||
CI:
|
||||
commitlint → history Gitleaks / audits
|
||||
→ Prisma generate → verify
|
||||
→ test:e2e → build again → isolated SQLite suites
|
||||
```
|
||||
|
||||
Источники: `backend/.ai/conventions/engineering.md:58–66`, `backend/.ai/conventions/testing.md:38–57`, `backend/.gitea/workflows/verify.yml:84–89`, `backend/package.json:28–34`.
|
||||
|
||||
### Docs / specs / deploy
|
||||
|
||||
- Docs: Markdown/link lint + local diff check; CI content/security/commit checks.
|
||||
- Specs: local `git diff --check`; CI устанавливает Node dependencies и повторяет эту команду на чистом checkout. Native OpenSpec validation документирована отдельно, но не вызывается этим job.
|
||||
- Deploy: отдельный ручной lifecycle и shell harness, вне application CI.
|
||||
|
||||
## Матрица
|
||||
|
||||
Частота ниже — **сценарная**, не измеренная: один local final verify, один commit и один CI event. Для checks outside этой цепочки указан их фактический trigger.
|
||||
|
||||
| Mechanism | Purpose / consumers | Overlap и частота | Cost | Recommendation | Confidence |
|
||||
|---|---|---|---|---|---|
|
||||
| 1. TypeScript | Source/tooling diagnostics; local/CI | FE: 4 calls в двух verify. BE: по 2 app/test calls; test включает app | Повтор compiler invocation; incremental может удешевлять | Убрать FE второй call; BE app из aggregate | High |
|
||||
| 2. ESLint | Syntax, async, security/style policies | FE: 2 full + staged hook; scopes различаются | Typed lint всего проекта | KEEP full CI; local scoped; staged сохранить | High |
|
||||
| 3. Lint-config tests | Scope/exceptions/custom fixers | 2 full executions; часть заново доказывает presets | Synthetic lintText/config calculations | Сократить generic parity; project probes по tooling trigger | High |
|
||||
| 4. Knip | Dead exports/dependencies/entries | 2 executions; TS не эквивалентен | Whole-project analysis | KEEP CI; local по соответствующему изменению | High |
|
||||
| 5. Production dependency graph | FSD/package/test boundaries, cycles | FE: local + hook + CI = 3 | Hook дополнительно builds packages | Перенести из каждого commit в scoped local/CI | High |
|
||||
| 6. Story reachable graph | Catalog не достигает application data | 2 executions; base config включён, но graph semantics отличаются | Второй graph traversal | KEEP: не объединять вслепую | High |
|
||||
| 7. Generated OpenAPI freshness | Snapshot ↔ types bytes | 2 executions; TS не заменяет | Custom temp/subprocess machinery | Native `--check` | High |
|
||||
| 8. Unit/RTL/MSW tests | Own behavior, transport/UI results | Обычно 2 full runs; local/CI разные trust boundaries | Whole suite local | Targeted local, full CI | High |
|
||||
| 9. Browser layout | Реальная геометрия narrow/wide | Два viewport instances на run | Chromium/runtime setup | KEEP для geometry changes и CI | High |
|
||||
| 10. Coverage | FE floor; BE report | По одному сбору на full test run | Instrumentation/reporting | CI report; local targeted без coverage | High |
|
||||
| 11. Application build | Emission/bundling/assets | BE CI два builds подряд; Docker — отдельная boundary | Prisma/Nest/Vite work | Убрать same-job rebuild; Docker оставить | High |
|
||||
| 12. Storybook build | Catalog bundle | Local/CI по одному; app build не заменяет | Отдельный bundle | CI/catalog-risk scope | High |
|
||||
| 13. SQLite HTTP/integration | DB constraints, guards, contract | CI 19 functional suites; standalone 20-я performance отдельно | Per-suite migration + Jest | KEEP independent DB isolation | High |
|
||||
| 14. Runner regression | Isolation, cleanup, signals | Пять успешных `--all` + failure/signal paths вручную | 95 successful suite executions | Representative two-suite runs | High facts / Medium cut |
|
||||
| 15. Historical durability | Migration/rollback/restore | Schema/data-risk trigger | Separate historical build/app runs | KEEP risk-triggered | High |
|
||||
| 16. Image smoke | Actual image runtime/assets/persistence | Publication/manual; host checks не эквивалентны | Docker lifecycle | KEEP | High |
|
||||
| 17. Publication model test | Retry/digest failure branches | Только explicit invocation найден | Stateful command double, YAML block extraction | KEEP focused, не every-change gate | Medium |
|
||||
| 18. Staged secret scan | Не допустить secret в commit | Каждый FE commit | Index scan | KEEP; history scan позднее | High |
|
||||
| 19. History secret scan | Secrets во всей Git history | PR/push/schedule | Full-history scan/download | KEEP; оптимизировать только по измерениям | High |
|
||||
| 20. Hook security proof | Index vs worktree, redaction/fail-closed | FE security CI | Temp Git repo/real scanner | KEEP; разные гарантии с №19 | High |
|
||||
| 21. Dependency audit | Known advisories | CI; BE runtime/dev thresholds различаются | Network/advisory processing | KEEP; overlap не доказан одинаковыми properties | High |
|
||||
| 22. Commitlint | PR title и retained commits | CI 2 invocations; local commit hook дополнительно | App repos устанавливают полный manifest для job | KEEP policy; не добавлять custom parser | High |
|
||||
| 23. Release preparation guard | Branch/tree/version lifecycle | Explicit release prepare | Несколько Git queries | KEEP; preview не считать proof custom hook | High |
|
||||
| 24. Image version/digest binding | Tag/package/lock/revision/published bytes | Release-tag workflow | Metadata/registry checks | KEEP release boundary | High |
|
||||
| 25. Scanner checksum | Downloaded scanner bytes | 4 repo-local security paths | Один hash command | KEEP | High |
|
||||
| 26. Markdown/link checks | Docs syntax/local targets | 2 disjoint lint commands/run | 50 Markdown files | KEEP; не второй custom link checker | High |
|
||||
| 27. `git diff --check` | Working diff whitespace/conflict markers | Local useful; docs/specs CI clean checkout малоинформативен | Specs CI ещё устанавливает deps | KEEP local; удалить пустой CI work | High |
|
||||
| 28. Native OpenSpec validation | Schema/change/spec structure | Manual; v2 instructions требуют после 7 stages | Repeated CLI/context work | Только explicitly selected SDD; proportional scope | High facts / Medium policy |
|
||||
| 29. Deploy preflight/lifecycle tests | Lock, backup, ordering, failure cleanup | Manual deploy/harness | Docker doubles/real locks/files | KEEP | High |
|
||||
|
||||
### Важные разграничения
|
||||
|
||||
- **Local и CI не объявлены дубликатами только потому, что запускают одинаковую команду.** Сокращается обязательный local scope, CI остаётся независимой проверкой.
|
||||
- Production и Storybook dependency-cruiser отличаются `tsPreCompilationDeps: true/false`: `frontend/dependency-cruiser.cjs:78–87`, `frontend/.storybook/dependency-cruiser.cjs:22`. Один простой запуск не доказан эквивалентным двум.
|
||||
- OpenAPI generator parity и consumer type freshness проверяют разные отношения.
|
||||
- Coverage, browser layout и JSDOM behavior не взаимозаменяемы.
|
||||
|
||||
# D. SOLID / DRY / KISS / YAGNI Findings
|
||||
|
||||
## SRP
|
||||
|
||||
### D1. Test-only orchestration внутри production metadata service
|
||||
|
||||
`InstrumentMetadataService.read` совмещает access, saved lookup, demand start и work lookup, но production callers используют отдельные existing methods.
|
||||
|
||||
- Owner: `backend/src/integrations/tbank/instrument-metadata.service.ts:41–63`.
|
||||
- Production примеры: `backend/src/modules/investments/portfolio/application/portfolio.service.ts:122–123,275–283,309–315`, `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:213–216`, `backend/src/modules/investments/upcoming-events/upcoming-events.service.ts:200–206`.
|
||||
- Test consumer: `backend/test/tbank-metadata-persistence.e2e-spec.ts:87–382`, 38 call sites.
|
||||
|
||||
**Вывод:** убрать совмещённый API после перевода тестов на production-used boundary. Не удалять persistence suite и её concurrency/restart proofs.
|
||||
|
||||
### D2. Portfolio имеет ложную DI-зависимость и два имени одной операции
|
||||
|
||||
- Неиспользуемый `TbankMembershipService` constructor dependency: `backend/src/modules/investments/portfolio/application/portfolio.service.ts:9–18,45–53`.
|
||||
- Public `request` → private `queueSnapshot` без дополнительной policy: тот же файл, `234–243`.
|
||||
- Тест обходит public operation и читает private repository: `backend/test/tbank-value-history.e2e-spec.ts:99–111,247–256,307–313`.
|
||||
|
||||
**Вывод:** один public `request`, без unused DI; тест получает существующий repository provider напрямую. Не дробить queue/publication lifecycle.
|
||||
|
||||
## OCP / YAGNI
|
||||
|
||||
### D3. Plural authenticated-query boundary существует ради теста
|
||||
|
||||
`frontend/src/shared/api/authenticated-query.ts:15–32,61`; единственный consumer — `frontend/src/shared/api/authenticated-query.test.tsx:9–23`.
|
||||
|
||||
Тестовая plural branch содержит один query и берёт `[0]`. Приложение потребляет singular hook.
|
||||
|
||||
**Вывод:** удалить plural implementation и вариант test matrix. Singular auth gating оставить.
|
||||
|
||||
### D4. InstrumentSearch сохраняет stock-вариант без product consumer
|
||||
|
||||
- Union contract: `frontend/src/features/instrument-search/ui/InstrumentSearch/InstrumentSearch.view.tsx:18–31`.
|
||||
- Единственный production renderer consumer — BondsPage: `frontend/src/pages/bonds/ui/BondsPage/BondsPage.view.tsx:26–113`.
|
||||
- Stock-конфигурация используется тестом: `frontend/src/features/instrument-search/ui/InstrumentSearch/InstrumentSearch.test.tsx:21–29`.
|
||||
|
||||
**Вывод:** специализировать current renderer под bond, сохранив debounce, URL/search states и presentation cases. Не объединять bond search со stock screener.
|
||||
|
||||
### D5. Малые невостребованные элементы поверхности
|
||||
|
||||
| Элемент | Evidence | Минимальное действие |
|
||||
|---|---|---|
|
||||
| `history.retry` | `frontend/src/entities/broker-operation/model/useBrokerageOperationHistory.ts:52–61,76`; все три VMs его не читают | Удалить action; automatic invalid-cursor recovery `37–50` оставить |
|
||||
| `isFetching` input | `frontend/src/shared/lib/initial-availability.ts:10–16,18–46`; не читается | Удалить из type, пяти callers и fixtures |
|
||||
| `membershipWork()` | `backend/src/integrations/tbank/tbank-access.service.ts:90–97`; callers не найдены | Удалить метод, не одноимённое repository result field |
|
||||
| `candleTail` TTL key | `backend/src/integrations/tbank/tbank-source.policy.ts:3–8`; production references отсутствуют | Удалить key; настоящую candle coverage/tail policy оставить |
|
||||
| `HourlySyncSectionDto` | `backend/src/modules/investments/tbank-sync/tbank-sync.dto.ts:232–250`; current problems DTO другой, `271–272` | Удалить непривязанный class |
|
||||
| Export `totalsByCurrency` | `backend/src/modules/investments/portfolio/infrastructure/tbank-portfolio.mapper.ts:56,109–141`; используется внутри файла | Удалить export, не calculation |
|
||||
|
||||
## ISP / DIP
|
||||
|
||||
### D6. String DI alias для единственной реализации
|
||||
|
||||
- Structural contract/string token: `backend/src/modules/investments/instruments/stocks/application/stock-screener.service.ts:49–63`.
|
||||
- Единственный `useExisting` alias: `backend/src/modules/investments/instruments/instruments.module.ts:23–25`.
|
||||
- Alternative implementations/overrides token не найдены.
|
||||
|
||||
**Вывод:** injection по существующему concrete class token. Узкий type можно сохранить через `Pick`, как уже сделано в `backend/src/modules/investments/tbank-connection/tbank-connection.service.ts:11–19`.
|
||||
|
||||
Это небольшой cleanup, не повод создавать interface.
|
||||
|
||||
### D7. FormProvider без context consumers
|
||||
|
||||
`frontend/src/features/auth/ui/AuthForm/AuthForm.tsx:50–86`: поля используют локальный `methods.register`, descendants не используют form context.
|
||||
|
||||
Но `frontend/.ai/conventions/ui.md:128–129` предписывает `FormProvider`.
|
||||
|
||||
**Вывод:** сначала сделать existing rule условным по наличию context consumers, затем убрать provider здесь. Не создавать consumers ради оправдания wrapper.
|
||||
|
||||
## LSP
|
||||
|
||||
Для основной business architecture **практически неприменим**: нет собственной иерархии взаимозаменяемых domain implementations. Concrete providers/repositories — не нарушение LSP или DIP.
|
||||
|
||||
DTO inheritance может сохранять Swagger metadata/schema identity — например `backend/src/modules/investments/instruments/bonds/presentation/bond.dto.ts:5–16`. Удалять пустые DTO subclasses по визуальному сходству нельзя.
|
||||
|
||||
## DRY
|
||||
|
||||
Подтверждены узкие дубли knowledge/assertions:
|
||||
|
||||
- Anonymous `/accounts` redirect повторён table case и standalone case: `frontend/src/app/routes/AppRoutes/AppRoutes.test.tsx:35–61`.
|
||||
- Failed-image behavior повторяется на page level, хотя сильнее проверен EntryCard: `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:166–169`, `frontend/packages/ui/src/EntryCard/EntryCard.test.tsx:24–63`.
|
||||
- Process authority повторяет approval/cascade/context policies в canonical specs, schemas и config: `specs/openspec/config.yaml:55–58`, `specs/openspec/specs/openspec-authority-and-lifecycle/spec.md:53–80`, `specs/openspec/schemas/wealth-ledger-sdd-v2/schema.yaml:554–566,625–642`.
|
||||
|
||||
Не рекомендовано объединять:
|
||||
- короткие local money fixtures;
|
||||
- worker implementations с разной publication semantics;
|
||||
- repo-local release configs;
|
||||
- похожие UI/domain states с разными responsibilities.
|
||||
|
||||
## KISS
|
||||
|
||||
### D8. Motion dependency ради одного opacity transition
|
||||
|
||||
- Единственный source consumer: `frontend/src/app/routes/RouteTransition.tsx:3–30`.
|
||||
- Единственный caller: `frontend/src/app/routes/RouteFallback.tsx:70–81`.
|
||||
- Dependency: `frontend/package.json:52`.
|
||||
|
||||
**Минимум:** локальный CSS fade-in с `prefers-reduced-motion`; сохранить `key={pathname}` и current mount boundary.
|
||||
|
||||
Потенциально удаляется **одна runtime dependency**. Bundle savings не измерены. Это replacement opportunity, а не unused package в текущем коде.
|
||||
|
||||
# E. Test Suite Complexity
|
||||
|
||||
## E1. Setup architecture
|
||||
|
||||
### Frontend
|
||||
|
||||
Общая инфраструктура небольшая:
|
||||
|
||||
- `frontend/src/test/query.tsx:5–24`: две Query factories, consumers — 7 и 20 test files.
|
||||
- `frontend/src/test/resource.ts:6–25`: одна resource-envelope factory, 20 test consumers.
|
||||
- `frontend/src/test/setup.ts:13–48`: MSW lifecycle, RTL cleanup, auth reset, browser substitutes.
|
||||
- `frontend/src/test/server.ts:1–7`: прямой MSW server.
|
||||
- `frontend/src/test/storybook.test.tsx:19–57`: isolation checks для девяти story modules.
|
||||
|
||||
**Общего самописного test framework не обнаружено.** Размножение локальных render/provider setups не требует автоматически создавать универсальный render helper.
|
||||
|
||||
### Backend
|
||||
|
||||
- 20 integration suites: 14 AppModule, 5 direct repository/service boundaries, одна migration suite.
|
||||
- 14 Nest bootstrap sites, 23 overrideProvider call sites.
|
||||
- 4 service factories, 4 deferred definitions, 7 polling helpers.
|
||||
- 8 CJS инфраструктурных/helper файлов, перечисленных в B2.
|
||||
|
||||
Fresh DB на suite, synthetic credentials и signal cleanup — необходимая стоимость. Не рекомендовано объединять все suites в общую DB ради меньшего setup.
|
||||
|
||||
## E2. Участки для сокращения
|
||||
|
||||
В таблице ниже сокращённые frontend filenames принадлежат указанным page/component directories; backend filenames — `src/modules` или `test` по полным путям в разделах D и H.
|
||||
|
||||
| Набор | Что защищает | Лишняя подготовка / overlap | Что теряется и решение |
|
||||
|---|---|---|---|
|
||||
| `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:13–28,40–61,83–205` | Route scope, loading/busy, events/empty/error presentation | Два module mocks и пять fake query shapes через `as never`; соседняя `AccountEventsPage.filters.test.tsx:50–108` уже использует MSW | **Упростить**, не удалять outcomes. Network cases — existing MSW; presentation-only — existing view |
|
||||
| `backend/src/modules/banking/bank-cards/application/get-bank-summary.use-case.spec.ts:11–48` | Category mapping, точное `1.23`, inconsistent category rejection | Fake Prisma tx + настоящий repository, хотя use case вызывает только `summaryData()` | Mock ближайшую repository boundary. DB proof не теряется: fake tx его не обеспечивал |
|
||||
| `backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:48–165` | Signed nanos, unknown type, cash/quantity/currency mapping | Background request, polling и `publish.mock.calls` для чистого mapper result | Первые два cases напрямую через existing `mapPortfolio`; orchestration/integration proof оставить |
|
||||
| `backend/src/modules/investments/operations/presentation/brokerage-operations.controller.spec.ts:1–18` | Передача args в mock service | Prisma mock, casts, assertion прямого вызова; configured HTTP cases покрывают account/query/access | **Удалить**. HTTP proof: `backend/test/tbank-ledger-local.e2e-spec.ts:113–137,194–227,336–366`, `backend/test/all-accounts.e2e-spec.ts:63–104` |
|
||||
| `frontend/src/app/routes/AppRoutes/AppRoutes.test.tsx:55–61` | Anonymous redirect | Более сильный table case уже на `35–53` | **Удалить standalone case** |
|
||||
| `frontend/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx:166–169` | Image error fallback | EntryCard test проверяет также same-source rerender/new-source reset | Удалить только error exercise; event→logo/initial assertions оставить |
|
||||
| `backend/test/tbank-metadata-persistence.e2e-spec.ts:87–382` | Reuse, coalescing, privacy, late publication, reopen, batch lookup | Сценарии завязаны на production-unused `read()` | **Перевести boundary**, не удалить suite |
|
||||
| `backend/test/bank-cards.e2e-spec.ts:371–389,710–753` | Rename/default role, durability, FK/unique | Три declarations зависят от предыдущих tests и их category state | Сделать три cases самодостаточными существующим `createCard`; не fixture framework |
|
||||
| `backend/test/sqlite-regression.cjs:20–22,77–91` | Runner isolation/cleanup/signals | Пять повторов всех 19 functional suites | По две representative suites/run; canonical full functional CI оставить |
|
||||
| `frontend/scripts/test-eslint.mjs:148–202` | Все recommended plugin rules включены | Exhaustive parity каждой library preset | Удалить blanket parity; оставить project exceptions/scopes/custom behavior probes |
|
||||
|
||||
## E3. Assertions против стоимости setup
|
||||
|
||||
Наиболее явные несоразмерности:
|
||||
|
||||
- **18 строк controller unit** поддерживают исключительно mock delegation.
|
||||
- **Две mock layers + пять query shapes** конкурируют с уже существующей HTTP boundary в одном page area.
|
||||
- **Пять полных SQLite runs** защищают свойства runner, не пять разных domain scenarios.
|
||||
- **Fake Prisma transaction** требуется для двух assertions чистого summary use case.
|
||||
|
||||
Большие financial fixtures или длинный crash setup сами по себе не плохи: они могут защищать boundary, недоступную дешёвому test level.
|
||||
|
||||
## E4. Что оставить
|
||||
|
||||
- Денежные unit cases: signed nanos, значения выше `2^53`, currencies, unknown≠zero.
|
||||
- Real SQLite CAS/transactions/replay/refund constraints.
|
||||
- Session transport/generation fencing.
|
||||
- Metadata/candle/operations late-publication и restart/crash cases.
|
||||
- Configured HTTP guards/pipes/cookie/status tests.
|
||||
- Browser geometry.
|
||||
- Historical migration/rollback/restore.
|
||||
- Story isolation, когда она доказывает отсутствие application network/storage access.
|
||||
|
||||
Примеры: `backend/src/modules/banking/bank-cards/domain/bank-card.calculations.spec.ts:46–159`, `backend/test/bank-cards.e2e-spec.ts:462–523,622–683`, `backend/test/app.e2e-spec.ts:161–213`, `backend/test/tbank-operations-persistence.e2e-spec.ts:74–100`.
|
||||
|
||||
**Не установлено, какие tests были созданы исключительно ради coverage.** Frontend floors (`frontend/vitest.config.ts:19–24`) создают constraint, но не доказывают происхождение тестов. Snapshot bloat не найден.
|
||||
|
||||
# F. AI Agent & Developer Workflow
|
||||
|
||||
## F1. Bootstrap и full gate обязательны слишком широко
|
||||
|
||||
Одинаковое требование в обоих application repos:
|
||||
|
||||
> Code/dependency change: `npm ci`, полный `npm run verify`…
|
||||
|
||||
`frontend/.ai/conventions/engineering.md:60–62`, `backend/.ai/conventions/engineering.md:60–62`.
|
||||
|
||||
Для code-only изменения в уже подготовленном checkout повторная установка не устанавливает новый инвариант. Full frontend verify также запускает browser tests, Knip, оба dependency graphs и Storybook build.
|
||||
|
||||
**Упростить:** bootstrap при новом checkout/изменении lockfile или toolchain; local checks по affected behavior; полный независимый CI gate сохранить.
|
||||
|
||||
Потеря — более поздний feedback по незатронутым областям, а не отсутствие merge-time проверки.
|
||||
|
||||
## F2. Pre-commit собирает packages и проверяет весь graph на каждом commit
|
||||
|
||||
`frontend/.husky/pre-commit:1–3`, `frontend/package.json:25,33`.
|
||||
|
||||
Даже Markdown commit достигает `npm run deps`, который сначала builds workspaces. UI/icons builds включают Vite и declaration compilation: `frontend/packages/ui/package.json:23–25`, `frontend/packages/icons/package.json:19–21`.
|
||||
|
||||
**Перенести graph check из hook в scoped local/CI.** Staged secret scan и staged ESLint оставить.
|
||||
|
||||
## F3. Mechanical lint policy создаёт искусственную форму кода
|
||||
|
||||
- Вложенные component helpers запрещены custom rule: `frontend/eslint-rules.mjs:63–85`.
|
||||
- Локальные Zod declarations/refinements требуют filename suffix: тот же файл, `20–36`.
|
||||
- JSX sibling spacing имеет собственный AST traversal/fixer: тот же файл, `104–175`.
|
||||
- Любой inline JSX callback/conditional prop запрещён: `frontend/eslint.config.mjs:273–283`.
|
||||
- Backend magic-number profile требует имена даже для `0`, `1` и bigint literals: `backend/test/eslint-config.cjs:78–92`.
|
||||
|
||||
Часть этих правил защищает readability preferences, а не data flow или runtime correctness. Они увеличивают число именованных declarations, файлов и exceptions.
|
||||
|
||||
**Упростить blanket restrictions**, оставив semantic boundaries: no data fetching во view, безопасные promises, stable query client, доступность, named business policy values. Не заменять custom policy ещё одним инструментом.
|
||||
|
||||
## F4. Generic skills конфликтуют с локальным соразмерным workflow
|
||||
|
||||
- `writing-plans` автоматически применим к multi-step requirements: `.agents/skills/workflow/writing-plans/SKILL.md:3`.
|
||||
- Предписывает файл в `docs/superpowers/plans`: тот же файл, `16–17`.
|
||||
- Добавляет fixed test/commit structure и Review Focus cases: тот же файл, `43–50,77–87,108–137`.
|
||||
- TDD применяется к refactoring и требует failing test до любого production code: `.agents/skills/workflow/test-driven-development/SKILL.md:18–45`.
|
||||
- Локальный Test Intent не требует отдельного artifact, исключает trivial wrapper/declaration testing: `frontend/.ai/conventions/engineering.md:49–55`, `backend/.ai/conventions/engineering.md:49–55`.
|
||||
|
||||
**Рекомендация:** ограничить применение этих workflows явно выбранной planning/TDD задачей и реальным regression risk. Сохранить reproduction для bug fixes и meaningful proof; не требовать новый тест для удаления unused field/export.
|
||||
|
||||
Стандартные OpenSpec skills изменять не требуется.
|
||||
|
||||
## F5. Opt-in OpenSpec против mandatory audit remediation
|
||||
|
||||
Противоречие:
|
||||
|
||||
- Opt-in: `specs/AGENTS.md:3–6`, `frontend/.ai/conventions/engineering.md:11–13`, `backend/.ai/conventions/engineering.md:11–13`.
|
||||
- Mandatory audit-derived registry/workflow: `specs/openspec/specs/project-foundation/spec.md:9–19`.
|
||||
- Обязательные snapshots/registry: `specs/openspec/specs/audit-remediation-workflow/spec.md:9–49`.
|
||||
- Docs запрещает размещение audit/research reports: `docs/AGENTS.md:12–14`.
|
||||
|
||||
**Упростить действующую process policy**, согласовав область действия и место artifacts. Не создавать registry/SDD change автоматически для реализации этого аудита.
|
||||
|
||||
## F6. Custom SDD workflow остаётся тяжёлым даже без behavioral delta
|
||||
|
||||
- V2: семь planning artifact groups.
|
||||
- При `skip_specs` остаются шесть.
|
||||
- Schemas: 885 строк; templates: 335 строк.
|
||||
- Семь stage instructions требуют native validation после записи.
|
||||
- Apply preconditions перечисляют семь explicit approvals: `specs/openspec/schemas/wealth-ledger-sdd-v2/schema.yaml:587–589`.
|
||||
|
||||
Это не доказательство бесполезности анализа/QA. Однако small docs/tooling change не всегда требует отдельных System Analysis, QA Analysis, Docs Impact и sessions.
|
||||
|
||||
**Сначала соблюдать opt-in режим.** При отдельном пересмотре custom schema — соразмерный набор artifacts по реальному риску. Не удалять approval/data-safety boundaries вслепую.
|
||||
|
||||
## F7. Specs CI даёт мало гарантии за полноценный install
|
||||
|
||||
`specs/.gitea/workflows/verify.yml:17–22` → `npm ci` → `git diff --check`.
|
||||
|
||||
На чистом checkout команда не проверяет committed PR diff, semantic structure или OpenSpec artifacts. Зато npm `prepare` настраивает hooksPath: `specs/package.json:13`.
|
||||
|
||||
**Удалить нынешний пустой job**, предварительно установив required-check settings. Существующие native validations запускать для соответствующего изменения; этот аудит не требует нового verifier.
|
||||
|
||||
## F8. Что не является проблемой
|
||||
|
||||
- AGENTS routers уже ограничивают чтение affected topics.
|
||||
- Root и nested conventions не наследуются друг в друга — разные enum/style choices не конфликт.
|
||||
- Read-only archives/history не нужно удалять.
|
||||
- Повторение краткого stage-specific rule допустимо, если оно нужно executor текущей стадии.
|
||||
- Транзитивно избыточные schema edges нельзя удалять только по graph reachability: они могут влиять на CLI context inputs.
|
||||
|
||||
## Необходимые измерения
|
||||
|
||||
Без новых tools достаточно existing CI logs и session usage:
|
||||
|
||||
1. Duration каждой verify stage, cold/warm отдельно.
|
||||
2. Время workspace builds в local commands/hook.
|
||||
3. Частота local full verify и reinstall.
|
||||
4. CI critical path и full-history scan duration.
|
||||
5. Token/context volume на обычную задачу против explicitly selected SDD change.
|
||||
6. Какие gates действительно находят regressions и на какой boundary.
|
||||
|
||||
Сейчас денежная/временная экономия не оценена численно.
|
||||
|
||||
# G. DELETE / SIMPLIFY / KEEP
|
||||
|
||||
## DELETE — 18 подтверждённых точек
|
||||
|
||||
Удаление подразумевает сохранение указанного более сильного или эквивалентного механизма.
|
||||
|
||||
1. Custom implementation `check-openapi.mjs` — заменить native check.
|
||||
2. Второй TypeScript call в frontend verify.
|
||||
3. `typecheck:app` stage из backend full verify — test typecheck superset оставить.
|
||||
4. Повторный backend build перед SQLite в том же CI job.
|
||||
5. Невызываемый `runtime-check.cjs`.
|
||||
6. `useAuthenticatedQueries` и исключительно его test branch.
|
||||
7. Неиспользуемый `history.retry`.
|
||||
8. Неиспользуемый `isFetching` input.
|
||||
9. Неиспользуемая Portfolio membership DI dependency.
|
||||
10. String alias `TBANK_ACCESS` — concrete provider оставить.
|
||||
11. Невызываемый `TbankAccessService.membershipWork`.
|
||||
12. Неиспользуемый `candleTail` policy key.
|
||||
13. Непривязанный `HourlySyncSectionDto`.
|
||||
14. Неиспользуемый внешний export `totalsByCurrency`.
|
||||
15. Delegation-only `brokerage-operations.controller.spec.ts`.
|
||||
16. Повторный standalone anonymous `/accounts` route test.
|
||||
17. Повторный image-error exercise в AccountEvents page test.
|
||||
18. Пустой specs CI verify job — после проверки branch requirements.
|
||||
|
||||
Evidence и consequences перечислены в B–E. Это **18 элементов/стадий**, а не 18 файлов.
|
||||
|
||||
## SIMPLIFY
|
||||
|
||||
- Local gate scope и bootstrap frequency.
|
||||
- Dependency graph invocation в pre-commit.
|
||||
- SQLite runner regression scope.
|
||||
- Exhaustive preset parity и mechanical custom lint policy.
|
||||
- Metadata tests через production-used methods.
|
||||
- Portfolio `request`/private test access.
|
||||
- AccountEvents fake-query setup.
|
||||
- Pure mapper/use-case tests.
|
||||
- Три order-dependent bank cases.
|
||||
- Bond-only InstrumentSearch contract.
|
||||
- Motion → CSS.
|
||||
- FormProvider rule по наличию consumers.
|
||||
- Process specs/SDD activation.
|
||||
- Specs hook activation через явное подключение вместо npm lifecycle.
|
||||
- Visual SHA tables только там, где exports реально передаются отдельно от Git.
|
||||
|
||||
## KEEP
|
||||
|
||||
- Финансовая точность и business validation.
|
||||
- Intent fingerprint, unique IDs, versions, tombstones, refund transaction.
|
||||
- Refresh hashing/CAS, cursor signature/binding.
|
||||
- Connection revision и conditional publication.
|
||||
- Coverage/completeness/freshness как разные свойства.
|
||||
- Independent local/CI trust boundaries.
|
||||
- Per-suite DB isolation и crash/durability proofs.
|
||||
- Runtime OpenAPI parity и consumer artifact freshness.
|
||||
- Scanner checksum, exact image digest/revision checks.
|
||||
- Deployment lock, pending marker, backup staging, explicit recovery.
|
||||
- Repo-local небольшие configs/helpers вместо нового shared tooling package.
|
||||
- Исторические ADR/archives.
|
||||
- Реальные browser layout и Storybook isolation guarantees.
|
||||
|
||||
**Условный кандидат, не DELETE:** `InvestmentRefreshState` имеет ноль production callers, но четыре stories и catalog isolation consumer — `frontend/src/features/tbank-connection/ui/InvestmentRefreshState/InvestmentRefreshState.view.tsx:1–61`, `frontend/src/features/tbank-connection/ui/InvestmentRefreshState/InvestmentRefreshState.stories.tsx:1–52`, `frontend/src/test/storybook.test.tsx:11,38`. Его удаление уберёт catalog artifact; намерение owner не установлено.
|
||||
|
||||
# H. Top 20 Complexity Reduction Opportunities
|
||||
|
||||
Порядок — по ожидаемому сокращению engineering overhead. Экономия указана в механизмах/запусках, не в выдуманных секундах.
|
||||
|
||||
Сокращённые filenames в таблице однозначно относятся к полным путям, указанным в B–F.
|
||||
|
||||
| № | Файлы и доказательства | Текущий механизм → минимальное изменение | Что перестанет работать / ожидаемая экономия | Риск · Confidence |
|
||||
|---:|---|---|---|---|
|
||||
| **1** | FE/BE `.ai/conventions/engineering.md:58–66` | `npm ci` + full verify для любого code change → bootstrap по изменению inputs, local affected checks, full CI | Исчезнет обязательный ранний full-project proof; меньше reinstall/full suites на малую задачу | Medium · High |
|
||||
| **2** | `backend/test/sqlite-regression.cjs:20–22,77–91`; `backend/test/sqlite.cjs:10–17` | Пять successful `--all` runs → по две representative suites | Теряются повторные business runs внутри runner proof; **95 → 10** successful suite executions, canonical full CI остаётся | Medium · High facts |
|
||||
| **3** | `frontend/package.json:26`; `frontend/tsconfig.app.json:17`, `frontend/tsconfig.node.json:12` | Два TS calls с noEmit configs → один | Второй call не выполняется; минус один compiler invocation/full verify | Low · High |
|
||||
| **4** | `backend/package.json:22–34`; typecheck configs app `7–8`, test `7–8` | App subset + test superset → test superset в aggregate | Отдельная маркировка app-stage failure; минус один nonincremental TS pass | Low · High |
|
||||
| **5** | `backend/.gitea/workflows/verify.yml:84–89`; package `28,34` | Verify build, затем E2E build → после verify existing `node test/sqlite.cjs --all` | E2E stage перестанет пересобирать уже готовый output; standalone entrypoint сохранить | Low · High |
|
||||
| **6** | `frontend/.husky/pre-commit:1–3`; package `25,33`; UI/icons package builds | Whole graph + workspace build на каждый commit → local/CI graph | Потеря hook-time graph feedback; минус один workspace build/graph traversal на commit | Low–Medium · High |
|
||||
| **7** | `frontend/scripts/check-openapi.mjs:1–45`; CLI `125–134` | Custom temp/subprocess compare → `openapi-typescript … -o … --check` | Ничего из freshness guarantee; **45 строк custom orchestration** заменяются CLI option | Low · High; native check executed |
|
||||
| **8** | `specs/package.json:9,13`; `specs/.gitea/workflows/verify.yml:17–22` | Install + clean working diff check → удалить пустой job | Теряется малоинформативный check status; минус job/install/hooks side effect | Low после branch-setting review · High |
|
||||
| **9** | FE `eslint-rules.mjs:1–178`; `test-eslint.mjs:148–202,290–339` | Mechanical AST policy + exhaustive preset parity → оставить semantic boundaries и custom-exception probes | Меньше формальных style/shape guarantees; меньше custom rule/test maintenance | Medium · High facts |
|
||||
| **10** | `frontend/src/app/routes/RouteTransition.tsx:3–30`; package `52`; caller `RouteFallback.tsx:70–81` | Motion для fade-in → CSS с reduced motion | Library-driven fade-in заменяется; сохранить key/mount semantics; **−1 runtime dependency** | Low–Medium · High |
|
||||
| **11** | Metadata service `45–61`; persistence tests `87–382`; repository `159–170` | Test-only read/start API → tests через `request`/`savedMany`, затем удалить API/work accessor | Исчезнет unused combined operation; реальные reuse/race/restart proofs сохранить | Medium · High |
|
||||
| **12** | `frontend/src/shared/api/authenticated-query.ts:15–32,61`; test `9–23` | Unused plural generic hook → удалить и test-only variant | Multi-query convenience без product consumers; меньше generic surface/test matrix | Low · High |
|
||||
| **13** | Portfolio service `45–53,234–243`; value-history tests `99–111,247–256` | Unused DI + public/private alias → один `request`, прямой provider access в tests | Private test route исчезнет; queue/publication не меняется | Low · High |
|
||||
| **14** | History hook `52–61,76`; initial availability `10–46` | Невостребованное action и непрочитанный input → удалить | Никакой used behavior; automatic cursor recovery/wait budget остаются | Low · High |
|
||||
| **15** | InstrumentSearch view `18–31`; BondsPage view `26–113`; test `21–29` | Stock/bond renderer при одном bond consumer → bond-specific current contract | Исчезнет test-only stock mode; current product search не меняется | Low–Medium · High |
|
||||
| **16** | AccountEvents test `13–28,40–205`; filters test `50–108` | Fake hooks/query shapes → existing MSW/view boundaries | Исчезнут internal-hook assertions и пять fake shapes; visible outcomes сохранить | Medium · High |
|
||||
| **17** | Summary spec `11–48`; portfolio spec `48–165`; mapper `33–138` | Fake tx/background work ради чистого result → ближайший existing owner | Unit не будет exercise’ить orchestration; configured DB/publication proofs остаются | Low–Medium · High |
|
||||
| **18** | Controller spec `1–18`; route test `35–61`; page image test `166–169` | Delegation-only и одинаковые assertions → удалить узкие дубли | Только internal-call/repeated fallback assertions; более сильные HTTP/route/EntryCard proofs остаются | Low · High |
|
||||
| **19** | `backend/test/bank-cards.e2e-spec.ts:371–389,710–753`; setup `86–105` | Три tests с implicit prerequisites → собственный arrange через `createCard` | Теряется зависимость от suite history, не business guarantee; targeted tests дешевле понимать | Low–Medium · High |
|
||||
| **20** | Specs AGENTS `3–6`; foundation `9–19`; remediation `9–49`; writing-plans `16–17,77–87`; TDD `18–45` | Mandatory artifacts/SDD/TDD rules конфликтуют с opt-in/direct work → уточнить existing activation/scope | Не будет автоматических planning/registry artifacts для каждой audit-derived правки; меньше context/approval loops | Medium · High |
|
||||
|
||||
Отдельные дешёвые dead-surface cuts из G можно делать при связанном изменении owner. Ради удаления одного export не нужен самостоятельный архитектурный проект.
|
||||
|
||||
# I. Minimal Engineering Baseline
|
||||
|
||||
Это предложение сократить существующий workflow, **не новая convention или набор инструментов**.
|
||||
|
||||
## Local development
|
||||
|
||||
Оставить:
|
||||
|
||||
- Активировать local Node version.
|
||||
- Bootstrap при новом checkout или изменении dependency/toolchain inputs.
|
||||
- Targeted test на cheapest reliable boundary.
|
||||
- Relevant typecheck/lint.
|
||||
- Semantic review affected behavior и `git diff --check`.
|
||||
- Risk-triggered DB/browser/contract/durability proof.
|
||||
|
||||
Убрать обязательный `npm ci` и весь full-project gate после каждого code-only изменения.
|
||||
|
||||
Full verify остаётся доступным локально, но его обязательная независимая граница — CI.
|
||||
|
||||
## Pre-commit
|
||||
|
||||
Оставить frontend:
|
||||
|
||||
1. Staged Gitleaks с redaction/fail-closed.
|
||||
2. Staged ESLint.
|
||||
3. Commit-msg policy при подключённом hook.
|
||||
|
||||
Перенести whole dependency graph и workspace builds из pre-commit.
|
||||
|
||||
Specs `commit-msg` можно оставить; подключение hooks сделать явным вместо автоматического `prepare`. Это не отменит уже записанный hooksPath само по себе.
|
||||
|
||||
## CI
|
||||
|
||||
### Frontend
|
||||
|
||||
Оставить:
|
||||
|
||||
- Native OpenAPI freshness.
|
||||
- Один TypeScript pass.
|
||||
- Один ESLint run.
|
||||
- Behavioral tests + coverage.
|
||||
- Knip.
|
||||
- Production и Storybook graph checks с сохранением различающихся semantics.
|
||||
- Application и catalog builds.
|
||||
- Security и commit policy.
|
||||
|
||||
Lint-config tests сократить до project-specific scopes/exceptions/custom behavior; изменение tooling требует соответствующего focused proof.
|
||||
|
||||
### Backend
|
||||
|
||||
Оставить:
|
||||
|
||||
- Generated Prisma client до checks.
|
||||
- Один full-source/test typecheck.
|
||||
- ESLint.
|
||||
- Unit suite.
|
||||
- Один build.
|
||||
- Изолированные functional SQLite suites, включая runtime OpenAPI contract.
|
||||
- Security и commit policy.
|
||||
|
||||
После уже выполненного build использовать existing runner без повторного build. Performance и historical durability остаются отдельными risk-triggered командами.
|
||||
|
||||
### Docs / specs
|
||||
|
||||
- Docs Markdown/link lint сохранить.
|
||||
- Working-diff check оставить локально.
|
||||
- Specs пустой verify job удалить после проверки branch requirements.
|
||||
- Native OpenSpec validation использовать по изменению schema/spec/change и только в выбранном SDD scope.
|
||||
- Security/release/commit mechanisms сохранить.
|
||||
|
||||
## Testing layers
|
||||
|
||||
Минимально достаточные существующие layers:
|
||||
|
||||
1. Pure domain/unit — деньги, normalizers, mappers.
|
||||
2. RTL/MSW — UI, forms, route/session/transport behavior.
|
||||
3. Configured HTTP + real SQLite — pipes, guards, transactions, CAS, persistence.
|
||||
4. Browser — geometry/lifecycle, которые JSDOM не доказывает.
|
||||
5. Image/historical/deploy — соответствующие packaging/data-recovery risks.
|
||||
|
||||
Удалять одинаковый proof, а не целый layer.
|
||||
|
||||
## Architecture constraints
|
||||
|
||||
Оставить:
|
||||
|
||||
- Production не зависит от test code.
|
||||
- Package не зависит от app.
|
||||
- FSD public API/layer/cycle boundaries.
|
||||
- Presentation не выполняет application fetching.
|
||||
- Story graph не достигает application data.
|
||||
- Pure domain не зависит от persistence/transport.
|
||||
|
||||
Не добавлять interface перед каждым concrete provider, общий worker framework или новые архитектурные checks.
|
||||
|
||||
## AI instructions
|
||||
|
||||
Оставить:
|
||||
|
||||
- Local AGENTS router.
|
||||
- Owner/callers/invariants до изменения.
|
||||
- Сохранение чужих изменений.
|
||||
- Context по affected topics.
|
||||
- Meaningful verification evidence.
|
||||
- Opt-in OpenSpec.
|
||||
|
||||
Сократить:
|
||||
|
||||
- Universal plan-file creation.
|
||||
- Новый failing test для каждого mechanical refactor.
|
||||
- Mandatory full reinstall/full verify на любую правку.
|
||||
- Blanket FormProvider/VM/helper/style mechanics без current responsibility.
|
||||
- Повторные process authority descriptions и обязательные artifacts с конфликтующим размещением.
|
||||
|
||||
---
|
||||
|
||||
## Итог
|
||||
|
||||
**Наиболее выгодный путь: сократить обязательную частоту и scope проверок → удалить доказанную невостребованную поверхность → упростить test boundaries → согласовать процессную authority.**
|
||||
|
||||
Не требуется менять финансовую архитектуру, добавлять инструменты или объединять независимые repositories.
|
||||
|
||||
**Net:** точный line delta не подготовлен; подтверждена замена 45-строчного OpenAPI checker native CLI, 18 точек удаления и возможность убрать одну runtime dependency после CSS-замены. Экономия времени и токенов требует измерений.
|
||||
@@ -0,0 +1,347 @@
|
||||
# Технический аудит WealthLedger
|
||||
|
||||
Дата: 11 октября 2026 года.
|
||||
|
||||
# Executive Summary
|
||||
|
||||
**Главная проблема проекта — незавершённый переход между моделями получения данных, а не недостаток архитектурных слоёв.** Часовой обход, прежняя screen-driven синхронизация, online lookup и локальные snapshots местами сосуществуют с разными правилами обновления. Добавление новых сервисов и абстракций это усложнит; наибольшую пользу даст удаление старых путей и уточнение ответственности существующих владельцев.
|
||||
|
||||
Наиболее существенные системные проблемы:
|
||||
|
||||
1. **Выбор сохранённого прогноза может блокировать публикацию нового полного результата.**
|
||||
2. **Ошибки часового координатора могут обозначаться успехом; отдельные сбои persistence способны оставить обход незавершённым.**
|
||||
3. **Часовой обход не обновляет уже сохранённые календари выплат из-за политики `stable`.**
|
||||
4. **Frontend получает информацию о свежести и ошибках, но основной экран портфеля теряет её при передаче props.**
|
||||
5. **Часть тестов защищает старые сценарии или проверяет гонку без фактического запуска конкурирующей работы.**
|
||||
|
||||
При этом основа проекта обоснованна: модульный монолит, конкретные providers, точная денежная арифметика, SQLite-транзакции, изоляция подключения и generated OpenAPI. **Масштабный архитектурный рефакторинг не нужен.**
|
||||
|
||||
## Основание и ограничения аудита
|
||||
|
||||
Проверены текущие checkout:
|
||||
|
||||
| Репозиторий | Commit |
|
||||
|---|---|
|
||||
| `frontend` | `c882120` |
|
||||
| `backend` | `0ad84ff` |
|
||||
| `specs` | `fcdc805` |
|
||||
| `docs` | `e6fd4f6` |
|
||||
|
||||
Аудит выполнен read-only с использованием `ponytail-audit`. Изучены архитектура, владельцы поведения, вызывающие пути, рискованные реализации и связанные тесты/specs.
|
||||
|
||||
Проведены две лёгкие проверки:
|
||||
|
||||
- После `nvm use --silent` запущен существующий dependency-cruiser: **420 модулей, 1773 зависимости, нарушений настроенных границ и циклов не обнаружено**.
|
||||
- `git diff --no-index --stat` между существующим `backend/openapi.json` и frontend snapshot: **различий нет**. Это подтверждает совпадение файлов на диске, но не свежую генерацию относительно runtime.
|
||||
|
||||
Рабочие деревья четырёх репозиториев остались чистыми. Build, полные `verify`, SQLite/E2E и нагрузочные проверки не запускались. Ниже отдельно обозначены выводы по коду, архитектурные риски и предпочтения; runtime-воспроизведение дефектов в рамках аудита не выполнялось.
|
||||
|
||||
# Findings
|
||||
|
||||
## F1. Выбор forecast смешивает последний результат и запрошенное окно
|
||||
|
||||
**Priority: High · Confidence: High · Тип: дефект по коду**
|
||||
|
||||
- **Evidence:** `backend/src/modules/investments/portfolio/infrastructure/forecast.repository.ts:50–79`. При отсутствии точного `scopeKey` выбирается первый пересекающийся forecast без сортировки по времени. Затем `if (!previous && fallback) return fallback` прекращает обработку даже при наличии нового полного `candidate`. Окно входит в ключ: `upcoming-events.service.ts:160–173`.
|
||||
- **Problem:** успешный расчёт для следующего дня может уступить старому пересекающемуся окну. Для непересекающегося запроса fallback вообще не выбирается, хотя действующая spec требует успешный пустой результат сохранённого прогноза.
|
||||
- **Impact:** после успешного обхода пользователь может продолжать видеть прежние рассчитанные суммы и старое окно. При ежедневном сдвиге окна это способно продолжаться до исчезновения пересечения.
|
||||
- **Root cause:** идентичность сохраняемого результата и параметры HTTP-фильтра объединены в строковом `scopeKey`.
|
||||
- **Recommendation:** локально разделить выбор последнего полного forecast и фильтрацию запроса. Fallback должен сохраняться при неуспехе нового результата, а не блокировать новый полный результат только из-за другого окна. Сохранить существующие проверки зависимостей и атомарной публикации.
|
||||
- **Trade-off:** потребуется уточнить read/write-семантику внутри существующих service/repository и пересмотреть несколько тестов. Новые слои не нужны.
|
||||
|
||||
## F2. Часовой координатор не гарантирует корректное завершение ошибки
|
||||
|
||||
**Priority: High · Confidence: High для ветвей ошибок · Тип: дефект и архитектурный риск**
|
||||
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:93–110,178–218`. Общий `catch` вызывает `settle` без добавления проблемы; при пустом `failures` результат — `succeeded`. Ошибка `settleHourly` подавляется. `settledWorks` ждёт `running` без конечной границы. В `catalog-enrichment.service.ts:582–590` аварийный выход очищает process-state, но не завершает durable work rows.
|
||||
- **Problem:** исключение вне локальных обработчиков может превратиться в успешный обход. Незавершённая запись дочерней работы может удерживать polling бесконечно.
|
||||
- **Impact:** статус обхода вводит в заблуждение. Если hourly row остаётся `running`, `reserveHourly` не разрешает следующий обход в том же процессе — `tbank-sync.repository.ts:190–200`.
|
||||
- **Root cause:** завершение определяется количеством накопленных ошибок, а не фактом прохождения всех обязательных шагов; подавление persistence errors скрывает нарушение lifecycle.
|
||||
- **Recommendation:** считать незавершённый сценарий ошибкой, явно фиксировать причину общего `catch`, ограничить ожидание дочерней работы и обеспечить диагностируемое восстановление терминального состояния. Использовать текущие durable rows и logger.
|
||||
- **Trade-off:** больше явных error branches. Это необходимая защита lifecycle, а не избыточный defensive programming.
|
||||
|
||||
## F3. Часовой обход повторно использует календари без обновления источника
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: дефект политики обновления**
|
||||
|
||||
- **Evidence:** scheduler вызывает `events.refresh` — `tbank-hourly.scheduler.ts:166–175`. Далее `readSources(..., false)` приводит к `requestMany(..., false, ...)` — `upcoming-events.service.ts:182–239`, `catalog-enrichment.service.ts:113–125`. В `catalog-enrichment.service.ts:189–193,679–692` повторное обновление разрешено для `quote`, но `dividends` и `bond-events` относятся к `stable`.
|
||||
- **Problem:** успешно сохранённый календарь не обновляется при последующих обходах. Для `bond-events` ключ не меняется вместе с датой.
|
||||
- **Impact:** новые или изменённые выплаты могут отсутствовать в прогнозе, хотя часовой обход завершился успешно.
|
||||
- **Root cause:** календари выплат получили политику практически неизменяемого справочника.
|
||||
- **Recommendation:** в существующем hourly refresh принудительно обновлять именно календарные источники через уже имеющийся `force`, сохранив локальность обычных GET и reuse стабильных metadata.
|
||||
- **Trade-off:** дополнительные календарные запросы во время обхода. Их фактический бюджет следует проверить на representative портфеле.
|
||||
|
||||
## F4. Длительный обход не соблюдает правило пропуска часового срока
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: несоответствие текущей spec**
|
||||
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.repository.ts:189–201` проверяет только `now - createdAt >= hourMs`. `specs/openspec/specs/hourly-investment-sync/spec.md:19–25` требует: обход 10:00, завершившийся после 11:00, допускает следующий запуск не ранее 12:00.
|
||||
- **Problem:** после завершения в 11:10 текущая проверка разрешит запуск уже на ближайшем tick.
|
||||
- **Impact:** пропущенный срок фактически превращается в немедленный повтор после завершения длинного обхода.
|
||||
- **Root cause:** проверяется прошедшая длительность, а не следующий допустимый срок относительно начала попытки.
|
||||
- **Recommendation:** исправить вычисление eligibility в `reserveHourly`; проверить контролируемым временем короткий обход, пропущенный срок и восстановление после простоя.
|
||||
- **Trade-off:** следующий запуск иногда будет позже текущего поведения — именно это требуется сохранённой политикой.
|
||||
|
||||
## F5. Основной экран портфеля теряет metadata и скрывает ошибку перечитывания
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: дефект передачи данных**
|
||||
|
||||
- **Evidence:** VM возвращает `source: portfolio.meta` — `frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:125–135`, но container не передаёт `source` — `AccountPage.tsx:24–34`. Loaded view уже умеет показывать её — `components/AccountPageLoaded.view.tsx:55–59`. Тест `AccountPage.test.tsx:751–855` закрепляет отсутствие времени, предупреждения и retry после ошибки.
|
||||
- **Problem:** подготовленная информация теряется на последнем переходе VM → view. Состояние transport error также не представлено в loaded-модели.
|
||||
- **Impact:** пользователь видит сохранённые суммы без необходимого контекста свежести и неудачного обновления.
|
||||
- **Root cause:** рассогласование props между container и view; тесты проверяют «тихое сохранение», а не требуемое восстановление.
|
||||
- **Recommendation:** передать существующий `source`; представить ошибку перечитывания и retry существующими UI-компонентами, сохранив cached snapshot.
|
||||
- **Trade-off:** экран покажет дополнительные статусы. Новая архитектура представления не требуется.
|
||||
|
||||
## F6. Income GET всё ещё может запускать личную синхронизацию
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: нарушение заявленной границы read**
|
||||
|
||||
- **Evidence:** `backend/src/modules/investments/operations/application/brokerage-operations.service.ts:408–435`: обычный `collectRanges` вызывает `membership.request` и `sync.readPlan`. Controller передаёт `observe=false`, если `sync=observe` отсутствует — `income-analytics.controller.ts:24–79`. Действующая граница запрещает импорт из обычных personal reads — `specs/openspec/specs/tbank-data-sync/spec.md:9–14`.
|
||||
- **Problem:** endpoint аналитики одновременно читает и инициирует обновление membership/операций.
|
||||
- **Impact:** лишние RPC и два владельца обновления одного источника; поведение обычного GET отличается от преимущественно тестируемого observe-пути.
|
||||
- **Root cause:** в `collectRanges` сохранился прежний request-driven сценарий.
|
||||
- **Recommendation:** убрать обновление membership из income read и оставить получение отсутствующих интервалов предусмотренному владельцу Ledger. До изменения уточнить cold-start поведение исторической аналитики.
|
||||
- **Trade-off:** неполная историческая аналитика не сможет незаметно инициировать загрузку. Этот UX-вопрос необходимо решить явно.
|
||||
|
||||
## F7. Политика перечитывания распределена между Query и собственным таймером
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: риск лишних запросов и несогласованная политика**
|
||||
|
||||
- **Evidence:** `frontend/src/app/ui/App.tsx:13–19` включает focus refetch, тогда как `.ai/conventions/data.md:70–74` описывает выключенный. Portfolio/operations/events/stock используют `staleTime: 30_000`; accounts — `1_800_000`. `features/tbank-connection/model/screen-sync.ts:22–35` напрямую вызывает `refetch` всех resources, не проверяя `isEnabled`. Например, `BondsPage.vm.ts:10–13` передаёт туда query, отключённый для короткого поиска.
|
||||
- **Problem:** несколько механизмов задают частоту GET. Ручной `refetch` обходит автоматическое отключение query через `enabled`.
|
||||
- **Impact:** возврат фокуса и повторное открытие создают запросы раньше описанного интервала; таймер может отправить запрос с неподготовленным identifier/query.
|
||||
- **Root cause:** периодическое чтение вынесено из владельца query, а разные поколения политики остались одновременно.
|
||||
- **Recommendation:** согласовать `staleTime`/focus policy; использовать существующий `refetchInterval` у query-owner. Если таймер сохраняется, фильтровать disabled resources. Удалить неиспользуемый `_scope`.
|
||||
- **Trade-off:** необходимо выбрать поведение фоновой вкладки и reconnect. Native Query policy уменьшит собственную координацию.
|
||||
|
||||
Семантика `enabled`, ручного `refetch` и `refetchInterval` сверена с актуальной документацией TanStack Query.
|
||||
|
||||
## F8. Часть race-тестов допускает ложноположительный результат
|
||||
|
||||
**Priority: Medium · Confidence: High для backend-примера, Medium для frontend-примера · Тип: дефект теста и риск**
|
||||
|
||||
- **Evidence:** `backend/test/tbank-local-reads.e2e-spec.ts:489–515` создаёт deferred portfolio, но запускает только GET, который теперь локален. `portfolioService.request` не вызывается. Во frontend `StocksPage.test.tsx:175–237` освобождает старый ответ и сразу проверяет уже существующий новый результат, не дожидаясь завершения старого GET.
|
||||
- **Problem:** backend-тест не создаёт позднюю публикацию; frontend-тест не гарантирует, что проверка произошла после неё.
|
||||
- **Impact:** тест может оставаться зелёным при удалении защиты, которую якобы проверяет.
|
||||
- **Root cause:** backend-сценарий не обновлён после изменения data flow; frontend-сценарий не имеет барьера завершения проигравшего запроса.
|
||||
- **Recommendation:** заменить backend-сценарий запуском реального source-owner с подтверждением начала RPC и завершения после смены подключения. Во frontend ждать завершения старого запроса/обработки его результата.
|
||||
- **Trade-off:** тесты станут немного явнее, но будут проверять реальный regression risk.
|
||||
|
||||
Дополнительно **не найдены прямые проверки `tick`/`reserveHourly` для нового координатора**. В ряде SQLite suites scheduler подменён stub — например, `backend/test/upcoming-events.e2e-spec.ts:140–147`.
|
||||
|
||||
## F9. Неиспользуемая manual screen-sync orchestration продолжает поддерживаться
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: лишняя сложность**
|
||||
|
||||
- **Evidence:** `backend/src/modules/investments/tbank-sync/tbank-sync.service.ts:54–311` содержит `request` и `dispatch`. Production controller предоставляет только status GET — `tbank-sync.controller.ts:31–41`; production callers `request` не найдены. `tbank-screen.ts` имеет единственного production-потребителя в этом пути; `tbank-screen.spec.ts` проверяет manual sync boundary.
|
||||
- **Problem:** недоступный сценарий сохраняет планирование, DI-зависимости, validation и тесты.
|
||||
- **Impact:** сотни строк для понимания и сопровождения модели, которая уже не запускается пользователем.
|
||||
- **Root cause:** прежний путь был отключён на HTTP-границе, но не удалён внутри.
|
||||
- **Recommendation:** удалить `request`/`dispatch`, их исключительно используемые helpers и тесты. Сохранить status/read compatibility, если она ещё нужна.
|
||||
- **Trade-off:** нельзя будет вернуть manual sync простым подключением старого метода; возвращать его сейчас оснований нет.
|
||||
|
||||
**Важно:** `planInvestmentScreen` нельзя удалять целиком — его использует текущий `BrokerageOperationsService`.
|
||||
|
||||
## F10. Online search создаёт fan-out и лишнюю waterfall-зависимость
|
||||
|
||||
**Priority: Medium · Confidence: High для числа вызовов · Тип: архитектурный риск эффективности**
|
||||
|
||||
- **Evidence:** `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:295–337`: один search RPC, до 30 detail RPC, затем batch prices. Лимит результатов — `catalog-selection.ts:5`.
|
||||
- **Problem:** на один поиск получается до **32 upstream-вызовов**; цены начинают загружаться только после всех details.
|
||||
- **Impact:** расход лимита T-Bank и рост времени ответа. Ошибка одного detail отклоняет весь `Promise.all`.
|
||||
- **Root cause:** online-путь сохранил полное enrichment каждого результата, хотя список и detail имеют разные потребности.
|
||||
- **Recommendation:** сразу запускать независимый prices request параллельно details. Отдельно проверить, какие detail-поля действительно нужны компактному списку, прежде чем убирать N запросов.
|
||||
- **Trade-off:** сокращение detail fan-out может уменьшить набор полей списка; без сверки потребностей UI это изменение не рекомендую выполнять вслепую.
|
||||
|
||||
Новый агрегирующий endpoint для решения этой проблемы не нужен.
|
||||
|
||||
## F11. Банковская история использует N+1 DB reads
|
||||
|
||||
**Priority: Medium · Confidence: High для структуры запросов, Medium для значимости затрат · Тип: риск эффективности**
|
||||
|
||||
- **Evidence:** `backend/src/modules/banking/bank-cards/application/bank-operations.service.ts:41–68,333–365`. Каждая строка вызывает `operationView`, повторно читающий карту, связанные операции, категорию и возвраты.
|
||||
- **Problem:** число reads растёт вместе с размером страницы; одинаковые связи загружаются многократно.
|
||||
- **Impact:** лишняя работа Prisma/SQLite внутри transaction. Это embedded DB, поэтому нельзя приписывать ей стоимость сетевой БД, но повторные queries остаются.
|
||||
- **Root cause:** mapper одной операции повторно используется как loader списка.
|
||||
- **Recommendation:** получить нужные связи и refund totals для всей страницы одним набором запросов и передать их в преобразование. Сохранить transaction и ownership checks.
|
||||
- **Trade-off:** list path станет немного отличаться от single-operation path. Реальный выигрыш в миллисекундах не измерялся.
|
||||
|
||||
## F12. Минимальная длина пароля имеет разные единицы
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: подтверждённое несоответствие**
|
||||
|
||||
- **Evidence:** backend — `src/modules/auth/presentation/auth.dto.ts:43–54,66–74`: минимум через `MinLength(12)`, максимум через UTF-8 bytes. Frontend — `src/features/auth/model/auth.schema.ts:36–42`: обе границы в байтах. Spec — `specs/openspec/specs/user-authentication/spec.md:10`.
|
||||
- **Problem:** например, шесть кириллических букв проходят frontend byte-minimum, но отклоняются backend character-minimum.
|
||||
- **Impact:** форма принимает пароль, который API не принимает.
|
||||
- **Root cause:** смешаны два способа измерения одной policy.
|
||||
- **Recommendation:** проверить обе границы в существующем byte-validator и убрать конфликтующий `MinLength`.
|
||||
- **Trade-off:** некоторые не-ASCII пароли с меньшим числом символов станут допустимы согласно текущему требованию.
|
||||
|
||||
Расхождение уже отмечено в docs, но от этого не перестаёт быть дефектом.
|
||||
|
||||
## F13. Online-пути не используют существующий перевод ошибки брокерского токена
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: дефект error mapping**
|
||||
|
||||
- **Evidence:** `backend/src/integrations/tbank/tbank.client.ts:553–557` бросает обычный `Error(INVALID_TBANK_TOKEN)`. Online calls в `catalog-discovery.service.ts:149–165,227–249,295–317` и `catalog-enrichment.service.ts:241–267` не переводят его. Общий filter отдаёт для обычного Error `500` — `common/filters/http-exception.filter.ts:19–31`. Translator уже существует: `tbank-token.service.ts:26–35`.
|
||||
- **Problem:** известная постоянная ошибка подключения выглядит как internal server error.
|
||||
- **Impact:** frontend GET transport может повторить заведомо неуспешный запрос; UI теряет корректный recovery code.
|
||||
- **Root cause:** новый online-путь обошёл прежнюю integration error boundary.
|
||||
- **Recommendation:** переиспользовать existing translator на online application boundary, сохранив обработку durable attempts.
|
||||
- **Trade-off:** появится явная связь online-пути с существующей token-error policy. Новый error registry не нужен.
|
||||
|
||||
## F14. Тесты MOEX закрепляют поведение, противоречащее актуальному требованию
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: конфликт контракта**
|
||||
|
||||
- **Evidence:** `backend/src/integrations/moex/moex.client.ts:182–185` считает отсутствующий блок ошибкой. `moex.client.spec.ts:121–191` и `test/upcoming-events.e2e-spec.ts:540–599` закрепляют это. `specs/openspec/specs/brokerage-account-upcoming-events/spec.md:82–84` требует трактовать отсутствие блока как отсутствие данных.
|
||||
- **Problem:** код и тесты согласованы между собой, но не с сохранённым поведением.
|
||||
- **Impact:** зелёные тесты не доказывают выполнение требования; прогноз может считаться неуспешным в сценарии, описанном как нормальное отсутствие событий.
|
||||
- **Root cause:** отсутствие блока объединено с повреждённым форматом ответа.
|
||||
- **Recommendation:** сначала согласовать смысл отсутствующего блока MOEX. Разделить отсутствие и malformed payload; не удалять проверки повреждённых строк/cursor.
|
||||
- **Trade-off:** неверная трактовка отсутствия блока способна превратить неполный календарь в подтверждённый пустой. Поэтому здесь требуется решение о семантике, а не механическое сокращение guards.
|
||||
|
||||
## F15. Дублируется whitelist-преобразование instrument
|
||||
|
||||
**Priority: Low · Confidence: High · Тип: возможность упрощения**
|
||||
|
||||
- **Evidence:** одинаковые `publicInstrument` в `backend/src/modules/investments/instruments/application/catalog-discovery.service.ts:499–530` и `catalog-enrichment.service.ts:646–677`.
|
||||
- **Problem:** одна boundary-модель имеет две поддерживаемые копии.
|
||||
- **Impact:** новое поле или изменение безопасной проекции необходимо повторять; возможен drift.
|
||||
- **Root cause:** online и persisted пути развивались отдельно.
|
||||
- **Recommendation:** оставить одну функцию у текущего instrument boundary owner.
|
||||
- **Trade-off:** один локальный import вместо независимых копий. Сам whitelist сохраняется.
|
||||
|
||||
## F16. Маршрут чтения документации ведёт к разным поколениям архитектуры
|
||||
|
||||
**Priority: Medium · Confidence: High · Тип: documentation drift**
|
||||
|
||||
- **Evidence:** `docs/architecture/containers.md:143–148` описывает запуск работы запросом и request-driven восстановление. `docs/adr/019-hourly-reads-and-online-instrument-reference.md:9–16` описывает нового владельца — hourly walk. В `specs/AI_INDEX.md:15–31` отсутствуют `hourly-investment-sync`, `tbank-data-sync` и `online-instrument-information`.
|
||||
- **Problem:** актуальные правила существуют, но основной маршрут чтения не приводит к ним последовательно.
|
||||
- **Impact:** разработчик может восстановить удаляемую модель или принять старое поведение за текущий invariant.
|
||||
- **Root cause:** обновлены отдельные требования/ADR, но не все текущие объясняющие страницы и индекс.
|
||||
- **Recommendation:** обновить существующий индекс и текущие архитектурные объяснения; подробную текущую механику держать в одной тематической странице, из остальных ссылаться на неё.
|
||||
- **Trade-off:** часть страниц станет менее самодостаточной, зато уменьшится число расходящихся копий.
|
||||
|
||||
Исторические ADR и архивы сами по себе дефектом не являются.
|
||||
|
||||
## F17. Отдельный presentation VM не имеет самостоятельной ответственности
|
||||
|
||||
**Priority: Low · Confidence: Medium · Тип: предпочтение сопровождаемости**
|
||||
|
||||
- **Evidence:** `frontend/src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts:1–20` содержит только два callback для доступных названий Pagination; единственный consumer — `StocksPage.view.tsx:208`. Общая convention требует выносить callbacks — `.ai/conventions/ui.md:72–89`.
|
||||
- **Problem:** для двух локальных presentation-функций добавлен отдельный маршрут навигации по коду.
|
||||
- **Impact:** небольшая дополнительная cognitive cost без установленного выигрыша в поведении или производительности.
|
||||
- **Root cause:** вероятно, формальное применение общего VM-паттерна.
|
||||
- **Recommendation:** при ближайшем изменении держать callbacks рядом с Pagination, сохранив доступные названия. Самостоятельный VM оставлять для формы, fetching или связного lifecycle.
|
||||
- **Trade-off:** меньше единообразия структуры. **Это необязательный cleanup, не архитектурный дефект.**
|
||||
|
||||
# Simplification Opportunities
|
||||
|
||||
Ниже — отдельный complexity-pass в формате `ponytail-audit`, от большего сокращения к меньшему:
|
||||
|
||||
- **`delete:`** удалить недоступные `TbankSyncService.request/dispatch`, исключительно используемую нормализацию и её тесты; оставить необходимый status reader. [`backend/src/modules/investments/tbank-sync/`]
|
||||
- **`delete:`** удалить frontend `observe`/`observationKey`, если подтверждено отсутствие внешних consumers: в production callers не найдены, вызовы есть только в тестах. Обычный `refetch` уже обслуживает текущий сценарий. [`frontend/src/shared/api/resource-query.ts:69–127`]
|
||||
- **`native:`** передать периодическое чтение TanStack Query `refetchInterval`; убрать собственный timer/ref coordination и неиспользуемые screen descriptors. [`frontend/src/features/tbank-connection/model/screen-sync.ts`]
|
||||
- **`shrink:`** объединить две одинаковые `publicInstrument`; сохранить одну безопасную проекцию. [`backend/src/modules/investments/instruments/application/`]
|
||||
- **`delete:`** удалить прямой passthrough-тест controller; identity/guard/query проверять существующей configured HTTP suite. [`backend/src/modules/investments/upcoming-events/upcoming-events.controller.spec.ts:1–20`]
|
||||
- **`yagni:`** объединить двухфункциональный presentation VM с его единственным view при ближайшем изменении. [`frontend/src/pages/stocks/ui/StocksPage/`]
|
||||
|
||||
**net: -~550 lines, -0 deps possible.**
|
||||
|
||||
Это приблизительная оценка наиболее очевидных сокращений, не размер подготовленного diff. Подтверждённых кандидатов на удаление dependency не установлено.
|
||||
|
||||
# Testing Assessment
|
||||
|
||||
## Что действительно полезно
|
||||
|
||||
| Проверки | Почему стоит сохранить |
|
||||
|---|---|
|
||||
| `backend/test/app.e2e-spec.ts:161–213` | Конкурентная refresh rotation проходит через реальный SQLite CAS; проверяется дальнейшая работоспособность победителя |
|
||||
| `backend/test/bank-cards.e2e-spec.ts`, особенно refund/concurrency/replay cases | Проверяются финансовые ограничения, версии, rollback и недопущение повторного создания |
|
||||
| Decimal, RUB-money, portfolio calculations | Различают точные суммы, валюты, неизвестные значения и ноль |
|
||||
| Operations persistence/coverage и forecast publication tests | Проверяют реальные инварианты хранения и согласованности, которые mock Prisma не доказывает |
|
||||
| Frontend RTL/MSW формы и auth transport | Проверяют видимый результат, отсутствие invalid requests, сохранение ввода и session boundary |
|
||||
| Browser layout tests | Проверяют свойства реального layout, недоступные JSDOM |
|
||||
| Отдельный performance benchmark | Измеряет конкретный persisted-read budget; уже исключён из обычного набора functional suites |
|
||||
|
||||
## Что удалить, заменить или перенести
|
||||
|
||||
| Проблема | Действие |
|
||||
|---|---|
|
||||
| Passthrough controller unit test | **Удалить**: сейчас проверяет вызов mock; заменить недостающее поведение assertion в существующем HTTP-тесте |
|
||||
| Late portfolio test без запуска source work | **Заменить** реальной управляемой гонкой — F8 |
|
||||
| Frontend race test без ожидания старого ответа | **Усилить** барьером завершения; probe сам по себе не проблема |
|
||||
| Тесты «тихого» cached error на AccountPage | **Пересмотреть ожидаемое поведение**: оставить сохранение данных, проверять предупреждение и retry — F5 |
|
||||
| Manual screen normalization tests | **Удалить вместе с недоступным production-сценарием**, не переносить в новую abstraction |
|
||||
| MOEX missing-block tests | **Согласовать ожидание с контрактом**, сохранить malformed/cursor checks — F14 |
|
||||
|
||||
## Самые важные недостающие сценарии
|
||||
|
||||
1. **Hourly lifecycle:** первый запуск, повтор в срок, длинный обход с пропуском срока, restart и persistence failure.
|
||||
2. **Два последовательных успешных календарных обновления:** изменившийся upstream действительно меняет сохранённый календарь.
|
||||
3. **Forecast следующего дня:** новый полный результат заменяет прежний; узкий GET фильтрует сохранённый результат; непересекающееся окно возвращает предусмотренную пустоту.
|
||||
4. **Обычный income GET без `sync=observe`:** не создаёт personal work.
|
||||
5. **Disabled query на timer/focus boundary:** неподготовленный identifier не вызывает request.
|
||||
6. **Online invalid broker token:** публичный code постоянной ошибки без транспортных повторов.
|
||||
7. **Пароль на UTF-8 границах:** frontend и configured backend принимают одинаковые значения.
|
||||
|
||||
Достаточно существующих Jest, RTL/MSW и SQLite runner. Общий процент покрытия здесь ничего не решает: основной недостаток — **несоответствие некоторых тестовых сценариев текущему data flow**.
|
||||
|
||||
# API & Data Flow: прослеженные сценарии
|
||||
|
||||
| Сценарий | Фактическая цепочка | Вывод |
|
||||
|---|---|---|
|
||||
| Открытие счёта | Page VM → несколько resource GET → локальные repositories → snapshots → VM/view | Локальная read-модель полезна; metadata теряется на последнем переходе |
|
||||
| Часовое обновление | Native timer → durable reservation → membership → portfolios → operations → calendars → forecast | Есть основания сохранить coordinator; ошибки lifecycle и stable calendars требуют исправления |
|
||||
| Поиск облигаций / online lookup | Debounced Query → backend discovery → search + N details + prices → DTO → карточки | Есть fan-out и ненужная последовательность prices после details |
|
||||
| Скринер акций | POST → полный Shares → отбор → ограниченный snapshot → GET страниц | Snapshot оправдан устойчивостью страниц; повторное получение каталога соответствует текущему требованию |
|
||||
| Создание банковской операции | Form intent → mutation → validation/transaction → fingerprint/version → DTO → invalidation | Защиты обоснованны; list reads можно сделать дешевле без изменения write-модели |
|
||||
|
||||
# Top 10 Improvements
|
||||
|
||||
**S** — локальная правка и небольшая проверка. **M** — несколько связанных владельцев/сценариев и integration proof.
|
||||
|
||||
| № | Изменение | Ожидаемый эффект | Сложность |
|
||||
|---:|---|---|---|
|
||||
| 1 | Передать `source` и восстановить error/retry presentation AccountPage — F5 | Пользователь получает контекст показанных сумм; очень маленький production diff | S |
|
||||
| 2 | Удалить недоступный manual screen-sync путь — F9 | Наибольшее сокращение кода, тестов и DI-навигации | S–M |
|
||||
| 3 | Свести периодическое чтение к query-owner — F7 | Меньше собственных timers и запросов с неподготовленными inputs | S–M |
|
||||
| 4 | Исправить vacuous race tests и добавить proof hourly lifecycle — F8 | Тесты начинают ловить реальные регрессии нового data flow | M |
|
||||
| 5 | Исправить выбор/публикацию последнего forecast — F1 | Устраняет удержание старого результата после успешного обновления | M |
|
||||
| 6 | Сделать завершение hourly ошибок явным — F2 | Убирает ложный успех и риск остановки последующих обновлений | M |
|
||||
| 7 | Обновлять календарные источники из hourly owner — F3 | Прогноз получает изменения выплат, а не только новые количества бумаг | S–M |
|
||||
| 8 | Убрать request-driven membership из income read — F6 | Один владелец personal refresh, меньше скрытых RPC | S–M, после ответа о cold history |
|
||||
| 9 | Согласовать byte-validation пароля — F12 | Убирает подтверждённое frontend/backend расхождение | S |
|
||||
| 10 | Переиспользовать invalid-token translator в online-путях — F13 | Корректный recovery code и отсутствие бесполезных повторов | S–M |
|
||||
|
||||
N+1 банковской истории и сокращение search detail fan-out разумны следующим этапом: структура затрат доказана, но фактический выигрыш и допустимый набор полей требуют уточнения.
|
||||
|
||||
# Do Not Change
|
||||
|
||||
1. **Модульный NestJS-монолит с конкретными providers.** Не обнаружена необходимость в обязательных ports/interfaces, CQRS или отдельном сервисе на каждый use case.
|
||||
2. **SQLite и single-process coordination.** Для текущей топологии это существенно проще распределённой системы. Ошибки lifecycle следует исправить внутри существующей модели.
|
||||
3. **Decimal strings и bigint-копейки.** Они сохраняют точность и смысл неизвестной суммы; замена на обычный `number` будет ухудшением.
|
||||
4. **Connection revision, CAS и conditional publication.** Они защищают от публикации результата под другим подключением. Это необходимая сложность.
|
||||
5. **Bank intent fingerprints, версии, tombstones и refund transactions.** Проверенные сценарии lost response, replay и конкурирующих изменений оправдывают эти механизмы.
|
||||
6. **Generated OpenAPI и производные frontend types.** Это не бесполезное дублирование моделей, а граница независимых репозиториев.
|
||||
7. **Snapshots скринера.** Они обеспечивают устойчивые страницы одного результата. Пересчитывать каталог при каждом GET страницы проще лишь внешне.
|
||||
8. **Mantine controls, React Hook Form и необходимые page VMs.** В формах и data-driven screens у них есть конкретная ответственность. Оснований дробить большие views по числу строк не найдено.
|
||||
9. **Общая тема для приложения и Storybook.** Два реальных consumers оправдывают visual foundation packages.
|
||||
10. **Исторические ADR и OpenSpec archives.** Их следует отличать от актуальных требований, а не удалять ради уменьшения количества файлов.
|
||||
|
||||
Признаки возможной AI-assisted сложности здесь объективны: недоступная orchestration, тесты старого сценария, дублирующие boundary mappers и формальная декомпозиция. **Происхождение кода по этим признакам не устанавливается.**
|
||||
|
||||
# Open Questions
|
||||
|
||||
1. **Forecast:** должен ли hourly walk сохранять новый полный результат для каждого нового дня независимо от HTTP-запросов? Как должен агрегироваться `all`, если сохранены отдельные account forecasts?
|
||||
2. **Исторический income:** кто загружает отсутствующие периоды после отказа от request-driven импорта? Должен ли пользователь предварительно открывать Ledger?
|
||||
3. **Клиентская свежесть:** правило «не чаще 30 минут» относится только к timer или также к focus, reconnect и повторному mount?
|
||||
4. **MOEX:** отсутствие запрошенного блока действительно подтверждает отсутствие событий или может означать неполный ответ? Сейчас spec и тесты отвечают по-разному.
|
||||
5. **Совместимость:** есть ли внешние consumers `GET /tbank-sync/:id` и observe-mode instrument endpoints? Это определяет объём удаления старого read compatibility.
|
||||
6. **Search UI:** какие поля требуют detail RPC для каждого результата, а какие уже присутствуют в search response?
|
||||
7. **Налоговая формулировка:** подтверждён ли смысл `dividend_net` для подписи «до налогов»? В docs это уже отмечено как неразрешённая интерпретация.
|
||||
8. **Рабочий масштаб:** сколько подключений и инструментов ожидается одновременно? Это нужно для оценки бюджета hourly calendar refresh и значимости N+1.
|
||||
|
||||
**Итог:** сначала устранить рассогласования нового data flow и сделать его тесты достоверными; затем удалить неиспользуемую координацию. Это даст больше пользы, чем добавление новых архитектурных практик или инструментов.
|
||||
@@ -0,0 +1,372 @@
|
||||
# Triage — 2026-10-11
|
||||
|
||||
## Правила и единица счёта
|
||||
|
||||
Основные источники — отчёты 01–15, 125 finding IDs из их конечных таблиц.
|
||||
Предыдущие источники: Technical F1–F17 и отдельная рекомендация удаления
|
||||
controller test (TA-X01); Complexity H1–H20 и девять дополнительных кандидатов
|
||||
(CA-X01–09); Efficiency RC-01–RC-18. Итого 190 исходных записей.
|
||||
Повторные executive summaries, G-list и D/F sections Complexity сопоставлены
|
||||
с H/X, а не посчитаны как новые независимые дефекты. CA-Hnn — номер строки
|
||||
раздела H; CA-Xnn перечислены ниже с точным предметом, поскольку исходных IDs
|
||||
у этих дополнительных предложений нет. TA-Fnn соответствует исходному Fnn.
|
||||
|
||||
`DUPLICATE` означает покрытие указанным deliverable; исходные confidence и
|
||||
ограничения сохраняются в отчёте. Дубликат INVESTIGATE не становится runtime
|
||||
FIX: недоказанная часть проверяется внутри соответствующего Issue до решения.
|
||||
Порядок исполнения не считается блокирующей зависимостью. Gitea владеет
|
||||
статусами; в реестре нет второго поддерживаемого status checklist.
|
||||
|
||||
## Нормализованные engineering topics
|
||||
|
||||
Каждый topic — цель с самостоятельно проверяемым результатом. Два local
|
||||
verification owners и четыре title-trigger owners поставляются независимо.
|
||||
Поэтому число Issues может превышать число topics. P0 не установлен.
|
||||
|
||||
| Topic | Priority / owner | Результат / Issue |
|
||||
| --- | --- | --- |
|
||||
| T01 | P1 backend | [#56: Hourly completion/recovery и executable proof](https://git.personal.ksv741.ru/WealthLedger/backend/issues/56) |
|
||||
| T02 | P1 backend | [#55: Latest complete forecast/identity/filtering/failure policy](https://git.personal.ksv741.ru/WealthLedger/backend/issues/55) |
|
||||
| T03 | P2 backend | [#54: Long-walk eligibility](https://git.personal.ksv741.ru/WealthLedger/backend/issues/54) |
|
||||
| T04 | P2 backend | [#53: Mutable calendars/fallback](https://git.personal.ksv741.ru/WealthLedger/backend/issues/53) |
|
||||
| T05 | P2 backend | [#57: Local Income/narrow Ledger interval](https://git.personal.ksv741.ru/WealthLedger/backend/issues/57) |
|
||||
| T06 | P2 backend | [#59: Online invalid-token translation](https://git.personal.ksv741.ru/WealthLedger/backend/issues/59) |
|
||||
| T07 | P2 backend | [#58: Accounts/Income hourly meta](https://git.personal.ksv741.ru/WealthLedger/backend/issues/58) |
|
||||
| T08 | P2 backend | [#60: Password bytes/artifact](https://git.personal.ksv741.ru/WealthLedger/backend/issues/60) |
|
||||
| T09 | P2 backend | [#61: Auth role/expiry/cookie regressions](https://git.personal.ksv741.ru/WealthLedger/backend/issues/61) |
|
||||
| T10 | P2 backend | [#62: Dead producer closure](https://git.personal.ksv741.ru/WealthLedger/backend/issues/62) |
|
||||
| T11 | P2 backend | [#64: Legacy reader compatibility](https://git.personal.ksv741.ru/WealthLedger/backend/issues/64) |
|
||||
| T12 | P2 backend | [#63: Source races/rollback/barriers](https://git.personal.ksv741.ru/WealthLedger/backend/issues/63) |
|
||||
| T13 | P2 backend | [#65: HTTP case isolation](https://git.personal.ksv741.ru/WealthLedger/backend/issues/65) |
|
||||
| T14 | P2 backend + frontend | [backend #66: Single preparation](https://git.personal.ksv741.ru/WealthLedger/backend/issues/66); [frontend #62: Lean local gates/hook](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/62) |
|
||||
| T15 | P2 backend | [#67: Compatible SQLite driver](https://git.personal.ksv741.ru/WealthLedger/backend/issues/67) |
|
||||
| T16 | P2 backend | [#4, reused: Compiler dependencies](https://git.personal.ksv741.ru/WealthLedger/backend/issues/4#issuecomment-1083) |
|
||||
| T17 | P2 backend | [#68: Banking page batches](https://git.personal.ksv741.ru/WealthLedger/backend/issues/68) |
|
||||
| T18 | P3 backend | [#69: Single publicInstrument projection](https://git.personal.ksv741.ru/WealthLedger/backend/issues/69) |
|
||||
| T19 | P3 backend | [#72: Small real runner workload](https://git.personal.ksv741.ru/WealthLedger/backend/issues/72) |
|
||||
| T20 | P2 frontend | [#54: Query-owned lifecycle/eligibility/API pruning](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/54) |
|
||||
| T21 | P2 frontend | [#55: Investment source/client feedback](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/55) |
|
||||
| T22 | P2 frontend | [#57: Banking read recovery](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/57) |
|
||||
| T23 | P3 frontend | [#58: Payment-system tuple](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/58) |
|
||||
| T24 | P2 frontend | [#56: Pending auth navigation](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/56) |
|
||||
| T25 | P2 frontend | [#61: Late-response client barriers](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/61) |
|
||||
| T26 | P2 frontend | [#59: CURRENCY visibility decision](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/59) |
|
||||
| T27 | P2 frontend | [#63: Events screen contract/oracles](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/63) |
|
||||
| T28 | P3 frontend | [#60: Native OpenAPI check](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/60) |
|
||||
| T29 | P2 specs | [#1, reused: Documentation authority](https://git.personal.ksv741.ru/WealthLedger/specs/issues/1#issuecomment-1080) |
|
||||
| T30 | P2 specs | [#28: Five capability index entries](https://git.personal.ksv741.ru/WealthLedger/specs/issues/28) |
|
||||
| T31 | P2 specs | [#29: Completion disposition](https://git.personal.ksv741.ru/WealthLedger/specs/issues/29) |
|
||||
| T32 | P2 specs | [#2, reused: Meaningful committed CI input](https://git.personal.ksv741.ru/WealthLedger/specs/issues/2#issuecomment-1067) |
|
||||
| T33 | P2 docs | [#34: Current system explanation](https://git.personal.ksv741.ru/WealthLedger/docs/issues/34) |
|
||||
| T34 | P3 frontend + backend + specs + docs | [frontend #64](https://git.personal.ksv741.ru/WealthLedger/frontend/issues/64), [backend #74](https://git.personal.ksv741.ru/WealthLedger/backend/issues/74), [specs #30](https://git.personal.ksv741.ru/WealthLedger/specs/issues/30), [docs #35](https://git.personal.ksv741.ru/WealthLedger/docs/issues/35): title edit proof |
|
||||
| T35 | P2 main | [#24: Generic applicability/evidence/approval/bootstrap](https://git.personal.ksv741.ru/WealthLedger/main/issues/24) |
|
||||
| T36 | P2 main | [#25: Private provider credential investigation](https://git.personal.ksv741.ru/WealthLedger/main/issues/25) |
|
||||
| T37 | P2 backend | [#70: Published screener constraints](https://git.personal.ksv741.ru/WealthLedger/backend/issues/70) |
|
||||
| T38 | P2 backend | [#73: Actual auth perimeter evidence](https://git.personal.ksv741.ru/WealthLedger/backend/issues/73) |
|
||||
| T39 | P2 backend | [#71: Independent prices/detail start](https://git.personal.ksv741.ru/WealthLedger/backend/issues/71) |
|
||||
|
||||
P1 обоснован финансовой publication/recovery boundary, а не частотой упоминаний.
|
||||
T34 понижен до P3: это metadata policy proof, не financial/security runtime bug.
|
||||
T17 допускает KEEP текущего loader по подтверждённому cost/maintenance trade-off.
|
||||
|
||||
## Реестр специализированных findings
|
||||
|
||||
| Finding | Decision | Topic / причина |
|
||||
| --- | --- | --- |
|
||||
| ARCH-002 | DUPLICATE | T05: оба live readPlan callers |
|
||||
| ARCH-003 | DUPLICATE | T20: timer принадлежит query lifecycle |
|
||||
| ARCH-005 | KEEP | Session owner, cookie queue, authenticated transport и refresh CAS |
|
||||
| ARCH-006 | KEEP | Singleton source owners, durable store и conditional publication |
|
||||
| ARCH-007 | KEEP | Cohesive banking transaction и exact money |
|
||||
| ARCH-008 | DUPLICATE | T02: event-specific selection проверяется вместе с financial fix |
|
||||
| ARCH-009 | DUPLICATE | T11: public observe compatibility требует evidence |
|
||||
| CPLX-001 | DUPLICATE | T20: native polling с сохранением session/cadence |
|
||||
| CPLX-002 | SIMPLIFY | T05: существующий operationIntervals вместо screen round-trip |
|
||||
| CPLX-003 | NO_ACTION | Optional label-only VM cleanup; текущая convention и a11y работают, отдельный результат не оправдан |
|
||||
| CPLX-004 | DUPLICATE | T02: semantics proof внутри fix; неизвестный GET publication outcome не объявлен defect |
|
||||
| CPLX-005 | DUPLICATE | T11: externally reachable reader, retirement не предрешён |
|
||||
| CPLX-006 | DUPLICATE | T05: cold Income decision до удаления request work |
|
||||
| CPLX-007 | KEEP | Complete forecast fences и exact currency totals |
|
||||
| CPLX-008 | KEEP | Durable reservation, attempts и source owners |
|
||||
| CPLX-009 | KEEP | Banking replay/CAS/tombstones/rollback |
|
||||
| CPLX-010 | KEEP | Stable screener snapshot, raw-input guard и latest intent |
|
||||
| DRY-001 | DUPLICATE | T08: byte-policy runtime boundary |
|
||||
| DRY-002 | SIMPLIFY | T18: одна projection, orchestration не объединяется |
|
||||
| DRY-003 | SIMPLIFY | T23: tuple schema/options, empty sentinel отдельно |
|
||||
| DRY-004 | SIMPLIFY | T21: общий access-loss membership predicate, recovery actions остаются локальны |
|
||||
| DRY-005 | KEEP | Independent browser/server validation и точные деньги |
|
||||
| DRY-006 | KEEP | Разные stock/bond payout/quote semantics |
|
||||
| DRY-007 | KEEP | Session/resource wrappers и accessible UI primitives |
|
||||
| DRY-008 | KEEP | Provider artifact, consumer derivative и unknown-code fallback |
|
||||
| DRY-009 | KEEP | Только live enrichment/shared fence, не dead discovery producer |
|
||||
| DRY-010 | DUPLICATE | T20: policy equivalence доказать до native migration |
|
||||
| DEAD-001 | DELETE | T10: manual writer closure, status reader сохраняется |
|
||||
| DEAD-002 | DELETE | T10: dead-parent catalog producer; shared publishSource сохраняется |
|
||||
| DEAD-003 | DELETE | T10: только orphan Accepted DTO |
|
||||
| DEAD-004 | DELETE | T20: only-test frontend observe API, backend query не удаляется |
|
||||
| DEAD-005 | DUPLICATE | T20: facade/descriptors удаляются после переноса live timer |
|
||||
| DEAD-006 | INVESTIGATE | T11: external status consumers и old screen rows |
|
||||
| DEAD-007 | DUPLICATE | T11: endpoint-specific observe support |
|
||||
| DEAD-008 | KEEP | Live interval/history helpers; keeper не требует сохранять round-trip caller |
|
||||
| DEAD-009 | KEEP | Registered hourly runtime owner |
|
||||
| DEAD-010 | KEEP | Candle first-load, staging и complete coverage |
|
||||
| DEAD-011 | KEEP | Shared source CAS, identities и candle FK |
|
||||
| FE-001 | FIX | T21: source metadata проходит VM/container/view |
|
||||
| FE-002 | FIX | T21: cached transport warning и read-only retry |
|
||||
| FE-003 | FIX | T20: disabled manual refetch и policy ownership |
|
||||
| FE-004 | FIX | T22: error не blank, sections independently recover |
|
||||
| FE-005 | FIX | T24: Fieldset не блокирует anchor |
|
||||
| FE-006 | INVESTIGATE | T26: tests/spec спорят о currency instrument |
|
||||
| FE-007 | DUPLICATE | CPLX-003: optional cleanup без самостоятельной задачи |
|
||||
| FE-008 | KEEP | Реальные page VMs и scroll/form presentation lifecycle |
|
||||
| FE-009 | KEEP | Semantic Mantine wrappers/package foundation |
|
||||
| FE-010 | KEEP | RHF draft, intent replay, explicit version adoption и bigint |
|
||||
| FE-011 | KEEP | Generated keys, session/connection/access boundaries |
|
||||
| FE-012 | NO_ACTION | Только unmeasured render hypothesis; нет симптома/scale input, profiler/virtualization backlog не нужен |
|
||||
| BE-001 | FIX | T02: complete candidate не veto старым overlap |
|
||||
| BE-002 | FIX | T01: failed/interrupted durable settlement и live-process recovery |
|
||||
| BE-003 | FIX | T04: mutable calendar refresh |
|
||||
| BE-004 | FIX | T05: local Income и bounded Ledger exception |
|
||||
| BE-005 | FIX | T06: все direct online callers используют translator |
|
||||
| BE-006 | FIX | T03: skipped terms вместо immediate repeat |
|
||||
| BE-007 | DUPLICATE | T10: writer closure already DEAD-001 |
|
||||
| BE-008 | FIX | T07: resource-relevant hourly meta |
|
||||
| BE-009 | KEEP | Простой modular monolith и concrete singleton DI |
|
||||
| BE-010 | KEEP | Banking transactions/replay и refresh CAS |
|
||||
| BE-011 | KEEP | Decimal strings/bigint и meaningful mappers |
|
||||
| BE-012 | KEEP | Revision/attempt/source fences, staged complete publication |
|
||||
| BE-013 | INVESTIGATE | T02: failed-existing-calendar GET fixture до publication policy change |
|
||||
| BE-014 | INVESTIGATE | T04: missing MOEX block contract внутри calendar boundary, malformed guards сохраняются |
|
||||
| BE-015 | INVESTIGATE | T01: controlled app.close/restart arm; graceful-stop необходимость не предрешена |
|
||||
| FLOW-001 | DUPLICATE | T05: ordinary Income/targeted Ledger source budget |
|
||||
| FLOW-002 | DUPLICATE | T20: common readiness boundary |
|
||||
| FLOW-003 | SIMPLIFY | T39: независимая prices/detail waterfall, прежний DTO и limiter |
|
||||
| FLOW-004 | DUPLICATE | T06: permanent failure не усиливается GET retry |
|
||||
| FLOW-005 | SIMPLIFY | T17: page loader adoption после isolated trace |
|
||||
| FLOW-006 | DUPLICATE | T04: calendar-specific refresh и multi-account coalescing |
|
||||
| FLOW-007 | DUPLICATE | T02: producer update отличается от read filter |
|
||||
| FLOW-008 | DUPLICATE | T20: focus/mount/reconnect/hidden-tab решение |
|
||||
| FLOW-009 | NO_ACTION | Local hydration/DTO price не измерена; нет оснований менять unlimited Income contract или добавлять BFF |
|
||||
| FLOW-010 | KEEP | Authorization, cursor, CAS, atomicity, whitelist и current reuse |
|
||||
| TQ-001 | FIX | T12: start source request перед replace, await done |
|
||||
| TQ-002 | FIX | T25: loser client settlement, не gate release |
|
||||
| TQ-003 | DUPLICATE | T01 + T03: real coordinator/reservation outcomes входят в scheduler acceptance |
|
||||
| TQ-004 | DUPLICATE | T21: заменить quiet oracle положительным route proof |
|
||||
| TQ-005 | DUPLICATE | T02: shifted-window publication и disjoint saved-empty |
|
||||
| TQ-006 | DUPLICATE | T04: conservative missing-block policy до provider evidence |
|
||||
| TQ-007 | FIX | T08 + T09: UTF-8 и token-role/cookie/expiry distinctions независимо |
|
||||
| TQ-008 | FIX | T12: throw после первого real tx write, snapshot + points unchanged |
|
||||
| TQ-009 | DUPLICATE | T10: normalization suite удаляется с подтверждённым dead writer |
|
||||
| TQ-010 | SIMPLIFY | T27: screen-owner решение, актуальные visible oracles; API filtering сохраняется |
|
||||
| TQ-011 | KEEP | Real auth CAS, usable winner и session generations |
|
||||
| TQ-012 | KEEP | Independent money oracles на complementary levels |
|
||||
| TQ-013 | KEEP | Deterministic SQLite rollback/dependency CAS/replay |
|
||||
| TI-001 | FIX | T13: case-owned bank prerequisites |
|
||||
| TI-002 | FIX | T13: existing scheduler override в unrelated HTTP/benchmark |
|
||||
| TI-003 | FIX | T12: started/release/done вместо readiness sleeps |
|
||||
| TI-004 | FIX | T14 backend: explicit fresh dist prerequisite targeted crash |
|
||||
| TI-005 | SIMPLIFY | T19: меньший настоящий SQLite payload, safety assertions прежние |
|
||||
| TI-006 | DELETE | T25: удалить unused failBond при adjacent async suite cleanup |
|
||||
| TOOL-001 | SIMPLIFY | T28: native read-only generator check |
|
||||
| TOOL-002 | FIX | T14 frontend: explicit no-install npx, occurrence не заявлена |
|
||||
| TOOL-003 | KEEP | Восьмистрочная manual diagnostic имеет допустимую boundary; нет доказанного external-unused/cost, автоматически не подключать/удалять |
|
||||
| TOOL-004 | KEEP | Targeted manual publication regression сохраняет digest/recovery proof; отсутствие blanket verify caller допустимо |
|
||||
| VERIFY-001 | FIX | T32: meaningful committed input, required context сохраняется |
|
||||
| VERIFY-002 | FIX | T34: четыре независимо поставляемых edited triggers |
|
||||
| VERIFY-003 | SIMPLIFY | T14 backend: одна preparation до consumers, fresh dist reuse |
|
||||
| VERIFY-004 | SIMPLIFY | T14 frontend: whole graph final/CI вместо every-commit |
|
||||
| DEPS-001 | FIX | T14 backend: explicit fresh quick-start preparation |
|
||||
| DEPS-002 | DELETE | T16: только direct ts-loader/source-map-support, штатный lock resolution |
|
||||
| DEPS-003 | SIMPLIFY | T15: adapter-compatible direct driver, не forced override |
|
||||
| DEPS-004 | FIX | T14 backend: debug development mode согласован с startup guidance |
|
||||
| RISK-001 | DUPLICATE | T01: abandoned running/live-process recovery |
|
||||
| RISK-002 | DUPLICATE | T01: outer exception никогда не false succeeded |
|
||||
| RISK-003 | DUPLICATE | T02: latest full forecast |
|
||||
| RISK-004 | DUPLICATE | T04: mutable calendar policy |
|
||||
| RISK-005 | DUPLICATE | T21: source/client feedback, cached sums сохранены |
|
||||
| RISK-006 | DUPLICATE | T06: safe permanent token error |
|
||||
| RISK-007 | DUPLICATE | T08: UTF-8 minimum, raw password |
|
||||
| RISK-008 | INVESTIGATE | T38: live proxy/admission evidence, не подтверждённая exploitation |
|
||||
| AGENT-001 | SIMPLIFY | T14 backend + frontend: separate installation/iteration/final boundary |
|
||||
| AGENT-002 | SIMPLIFY | T35: risk-based tests/evidence revision вместо message-count freshness |
|
||||
| AGENT-003 | FIX | T35: generic rulings внутри approved semantics; standard OpenSpec неизменен |
|
||||
| AGENT-004 | FIX | T35: conditional existing nvm.sh bootstrap, без нового script |
|
||||
| AGENT-005 | NO_ACTION | Tool/schema overhead не измерен; нет current blocker, global A/B infrastructure не создаётся |
|
||||
| AGENT-006 | SIMPLIFY | T35: model override только при tool capability |
|
||||
| SPEC-001 | SIMPLIFY | T29: одна documentation authority, audits в root по запросу |
|
||||
| SPEC-002 | FIX | T31: explicit original delivery disposition, unchecked archive не переписать |
|
||||
| SPEC-003 | FIX | T33: current Portfolio/Stock/Bond/Container claims сверены |
|
||||
| SPEC-004 | FIX | T30: пять topic entries, active screener не fake canonical |
|
||||
| SPEC-005 | FIX | T37: additionalProperties/lot constraints, runtime последствия unverified |
|
||||
|
||||
## Реестр предыдущих источников
|
||||
|
||||
Предыдущие измерения относятся к своим samples и не подтверждают runtime
|
||||
состояние нынешней системы. Служебные CA/TA IDs введены только для ссылок
|
||||
на уже существующие разделы, не как дополнительный finding source.
|
||||
|
||||
| Finding | Decision | Topic / причина |
|
||||
| --- | --- | --- |
|
||||
| TA-F01 | DUPLICATE | T02 |
|
||||
| TA-F02 | DUPLICATE | T01 |
|
||||
| TA-F03 | DUPLICATE | T04 |
|
||||
| TA-F04 | DUPLICATE | T03 |
|
||||
| TA-F05 | DUPLICATE | T21 |
|
||||
| TA-F06 | DUPLICATE | T05 |
|
||||
| TA-F07 | DUPLICATE | T20 |
|
||||
| TA-F08 | DUPLICATE | T12 + T25 + T01 |
|
||||
| TA-F09 | DUPLICATE | T10 |
|
||||
| TA-F10 | DUPLICATE | T39; full detail поля нужны actual bond cards |
|
||||
| TA-F11 | DUPLICATE | T17 |
|
||||
| TA-F12 | DUPLICATE | T08 |
|
||||
| TA-F13 | DUPLICATE | T06 |
|
||||
| TA-F14 | DUPLICATE | T04 |
|
||||
| TA-F15 | DUPLICATE | T18 |
|
||||
| TA-F16 | DUPLICATE | T30 + T33 |
|
||||
| TA-F17 | DUPLICATE | CPLX-003: optional labels cleanup не оправдывает задачу |
|
||||
| TA-X01 | KEEP | §Simplification Opportunities/controller test: audit 08 подтверждает полезный arbitrary-sub adapter seam; no blanket mock-test deletion |
|
||||
| CA-H01 | DUPLICATE | T14: installation/final gates |
|
||||
| CA-H02 | DUPLICATE | T19: real small runner workload |
|
||||
| CA-H03 | NO_ACTION | Второй FE tsc invocation не доказывает повторную recompilation/равноценность удаления; более точный audit 11 сохраняет |
|
||||
| CA-H04 | KEEP | App/test compiler roots различаются; superset не доказал эквивалентные diagnostics |
|
||||
| CA-H05 | DUPLICATE | T14 backend: prepared dist reuse |
|
||||
| CA-H06 | DUPLICATE | T14 frontend: lean hook |
|
||||
| CA-H07 | DUPLICATE | T28 |
|
||||
| CA-H08 | DUPLICATE | T32: исправить actual input, не удалить required check |
|
||||
| CA-H09 | NO_ACTION | Нет concrete false-positive/cost proof mechanical lint; прежняя blanket cut гипотеза не отменяет user decisions main #18 |
|
||||
| CA-H10 | KEEP | Motion реально используется, measured bundle/cost проблемы нет; native CSS — необязательная альтернатива |
|
||||
| CA-H11 | SIMPLIFY | T12: metadata tests на production-used request/savedMany; unused combined read удалять только после caller proof |
|
||||
| CA-H12 | DELETE | T20: only-test plural authenticated-query API после current caller inventory |
|
||||
| CA-H13 | SIMPLIFY | T12: existing public request/done и provider вместо private test access; publication owner не дробить |
|
||||
| CA-H14 | SIMPLIFY | T20: unused history.retry/initial isFetching surface после полного caller proof; automatic cursor recovery сохраняется |
|
||||
| CA-H15 | NO_ACTION | Малый stock/bond renderer union — лишь предыдущая гипотеза; нет current defect/существенной цены narrowing |
|
||||
| CA-H16 | DUPLICATE | T27: current Events RTL/MSW outcomes вместо old fake-query oracle |
|
||||
| CA-H17 | SIMPLIFY | T12: pure mapper/repository seam для unit proof, real SQLite guarantees отдельно |
|
||||
| CA-H18 | NO_ACTION | Blanket delegation/page fallback test deletion не обоснован primary TQ review; exact duplicate route case допустим opportunistically |
|
||||
| CA-H19 | DUPLICATE | T13: case-owned prerequisites |
|
||||
| CA-H20 | DUPLICATE | T29 + T35: opt-in/project applicability, no new workflow |
|
||||
| CA-X01 | NO_ACTION | D5 membershipWork accessor: prior-only small dead API hypothesis; removal при касании owner, не отдельный результат |
|
||||
| CA-X02 | NO_ACTION | D5 candleTail key: prior-only tiny unused field, candle loader KEEP |
|
||||
| CA-X03 | DELETE | D5 HourlySyncSectionDto: T10, только orphan DTO после schema/caller proof |
|
||||
| CA-X04 | NO_ACTION | D5 totalsByCurrency export: optional local API narrowing; calculation используется |
|
||||
| CA-X05 | DUPLICATE | G5 runtime-check: TOOL-003 KEEP manual boundary до external-consumer evidence |
|
||||
| CA-X06 | KEEP | D7 FormProvider: действующая local form convention, context-only optimization необязательна |
|
||||
| CA-X07 | KEEP | §SIMPLIFY/specs prepare: current explicit install lifecycle; нежелательный effect не подтверждён |
|
||||
| CA-X08 | KEEP | B4 visual SHA tables: отдельно передаваемые approval exports имеют integrity boundary, не удалять без proof |
|
||||
| CA-X09 | KEEP | G InvestmentRefreshState: четыре catalog stories и isolation consumer, не confirmed dead |
|
||||
| RC-01 | DUPLICATE | T14: один final gate после последнего relevant input change |
|
||||
| RC-02 | NO_ACTION | Aggregates repeated reads не кодируют semantic novelty; session-local reuse действует, cache service не нужен |
|
||||
| RC-03 | DUPLICATE | T14 + T35: reuse evidence по diff/command/outcome, без persistent validation ledger |
|
||||
| RC-04 | NO_ACTION | Multi-repo task scope законен; routers уже самостоятельны, hard permissions/manifest не оправданы sample count |
|
||||
| RC-05 | KEEP | Scoped independent semantic review сохраняется; elapsed task time не доказал waste |
|
||||
| RC-06 | NO_ACTION | Повторные tool calls могут быть intentional polling; pending/completed dedup framework не нужен |
|
||||
| RC-07 | DUPLICATE | T14 |
|
||||
| RC-08 | DUPLICATE | T14: standalone preparation сохраняется, composed boundary reuse |
|
||||
| RC-09 | DUPLICATE | T14 backend |
|
||||
| RC-10 | KEEP | Standard OpenSpec lifecycle/context refresh сохраняется по явному ограничению пользователя; mtime/hash cache не вводить |
|
||||
| RC-11 | NO_ACTION | 127-line unstaged root block отсутствует в проверенном HEAD; stale-context finding, current router 32 строки |
|
||||
| RC-12 | KEEP | Standard OpenSpec skills неизменны; явный request routing уже принадлежит local AGENTS |
|
||||
| RC-13 | DUPLICATE | AGENT-005 NO_ACTION: отсутствие measured routing cost не основание удалять skills |
|
||||
| RC-14 | DUPLICATE | AGENT-005 NO_ACTION: browser tools дают разные capabilities, default deletion не обоснован |
|
||||
| RC-15 | DUPLICATE | T14 frontend |
|
||||
| RC-16 | NO_ACTION | Global permission profiles не входят в Git behavior owner; новый management layer не обоснован |
|
||||
| RC-17 | INVESTIGATE | T36: historical inline credential claim не равен confirmed live exposure/P0; локальная приватная проверка и rotation при подтверждении |
|
||||
| RC-18 | NO_ACTION | Memory plugin activation не подтверждена, disabled/stale config не требует инфраструктуры |
|
||||
|
||||
## KEEP / NO_ACTION и границы
|
||||
|
||||
KEEP не создаёт Issues без конкретной задачи. Сохраняются exact money,
|
||||
server validation, nested ownership, auth CAS/cookie/session generations,
|
||||
connection/attempt/dependency fences, banking replay/version/tombstone,
|
||||
SQLite migrations/isolation/crash/restore, signed cursors, public projection,
|
||||
native OpenSpec phases и immutable history. Optional cleanup не становится
|
||||
mandatory backlog только из-за числа строк. Старые confidence/severity не
|
||||
переносятся автоматически на новый campaign priority.
|
||||
|
||||
## Dependencies и блокеры
|
||||
|
||||
- T04 зависит от T02 только в части проверенного failed-existing-calendar
|
||||
publication/fallback contract: force не должен создавать новую регрессию.
|
||||
- T10 имеет внутренний порядок удаления manual parent → exclusive discovery
|
||||
producer; отдельная Gitea dependency между файлами не нужна.
|
||||
- T27 финальная source-window presentation зависит от опубликованного T02
|
||||
artifact; договориться о UX можно до его реализации.
|
||||
- T11 не блокирует code-only T10/T20: public readers/DB identities сохраняются.
|
||||
- T14/T35 не блокируют P1. T01/T02/T03 independent, salvo common final diff checks.
|
||||
- T38 остаётся investigation до приватного proxy input, T36 — до локальной
|
||||
проверки актуального credential state; raw values и session IDs не публикуются.
|
||||
- Архивирование одного оригинала blocked: `АудитЭффективности.md` содержит
|
||||
private session metadata. Исходник сохранён; RC findings не потеряны.
|
||||
- Cold historical Income, hidden-tab/cadence, CURRENCY и Events filters —
|
||||
только named product questions внутри T05/T20/T26/T27, без guessed contract.
|
||||
|
||||
## Проверка и provenance
|
||||
|
||||
Discovery: все repositories доступны, Issues enabled, bot имеет push/pull.
|
||||
Remote commits пяти audited revisions подтверждены API. Gitea 1.27.3
|
||||
предоставляет native issue-dependencies endpoint; issue-types API не обнаружен.
|
||||
Labels организации переиспользуются: Priority/High=P1, Medium=P2, Low=P3;
|
||||
Kind/Bug, Enhancement, Testing, Documentation. Reviewed/status labels не
|
||||
используются для дублирования lifecycle. Assignees новых Issues не угадываются.
|
||||
|
||||
Все 44 participating Issues прочитаны обратно через API: 43 working и один
|
||||
[главный Issue](https://git.personal.ksv741.ru/WealthLedger/main/issues/23).
|
||||
Новых Issues 41 (40 working + navigation), reused 3. Working priorities:
|
||||
P1=2, P2=33, P3=8; navigation дополнительно P2. 39 topics, не 190 Issues.
|
||||
|
||||
| Source disposition | Count |
|
||||
| --- | --- |
|
||||
| FIX | 31 |
|
||||
| REFACTOR | 0 |
|
||||
| SIMPLIFY | 20 |
|
||||
| DELETE | 8 |
|
||||
| INVESTIGATE | 7 |
|
||||
| KEEP | 40 |
|
||||
| DUPLICATE | 67 |
|
||||
| NO_ACTION | 17 |
|
||||
| Total | 190 |
|
||||
|
||||
Source disposition и Issue kind — разные единицы: структурные simplification/
|
||||
deletion tasks используют existing Kind/Enhancement без дублирующего kind label.
|
||||
Оригинальные user-authored тела/assignees reused #4/#1/#2 сохранены; supplements
|
||||
даны comments с stable markers. Старые priority префиксы в titles исторические,
|
||||
актуальный campaign priority задаёт единственный Priority/Medium label.
|
||||
|
||||
Непривязанных actionable findings: 0. Все INVESTIGATE имеют Issue либо bounded
|
||||
proof внутри соответствующего fix. Два findings явно требуют недоступного
|
||||
внешнего private input: RISK-008 (actual proxy) и RC-17 (actual local credential
|
||||
owner); это 2 mapped external-evidence blockers, не подтверждённые runtime bugs.
|
||||
Product/compatibility decisions T05/T11/T20/T26/T27/T29 и original completion
|
||||
review T31 остаются named preconditions, сроки/ответы не выдуманы. Отдельный
|
||||
publication blocker — один original report, не потерянный finding.
|
||||
|
||||
Native dependency read-back: backend #53 → backend #55; frontend #63 → backend
|
||||
#55; specs #2 → specs #4 (исходные native toolchain criteria, Git-range portion
|
||||
можно исправить независимо). Других technical blocking edges нет, cycles нет.
|
||||
|
||||
Новые labels: 8 repo-scoped, только используемые. Organization label creation
|
||||
вернуло permission denial, resolved repo fallback; MCP issue update не меняет
|
||||
labels, resolved dedicated replace_labels для reused Issues. Необработанных
|
||||
API write failures нет. Milestone URLs проверены HTTP 200, due dates пусты.
|
||||
|
||||
Final read-back: 44 Issues и 44 уникальных stable markers, 0 template/metadata
|
||||
errors; у каждого ровно один priority, один primary kind, audit label и нужный
|
||||
milestone. 59 linked Git resources существуют на указанных commits; главный
|
||||
Issue перечисляет все 43 working Issues. Все 8 новых labels реально используются.
|
||||
Первая широкая параллельная HEAD-проверка прервана connection timeout, не
|
||||
ошибкой созданной сущности. Заменена адресным API/Git read-back и последовательным
|
||||
HTTP check: campaign, README, triage, original archive и все 5 milestones — 200.
|
||||
Этот verification transport failure обработан; полный parallel URL scan
|
||||
успешным не объявляется.
|
||||
|
||||
Дополнительные cross-audit signals/open questions не удваивают finding count.
|
||||
SDD DR-08 одноразовый pilot/transition — NO_ACTION до конкретной потребности;
|
||||
off-host backup/dummy hashing/logout bounded access/native lifecycle — KEEP
|
||||
существующих guarantees, без заявлений об их live verification. Ни история,
|
||||
ни role/security protections не удаляются ради неподтверждённой экономии.
|
||||
|
||||
Root gates: original/archived Git blob equality, redacted Gitleaks exit 0,
|
||||
staged allowlist review, semantic/Boy Scout review, diff check и commitlint.
|
||||
Первый archive push/read-back успешен; финальные links публикуются вторым commit
|
||||
в той же task branch. Полный application verify, build, tests, codegen,
|
||||
migration и реальная финансовая сеть в planning не запускались.
|
||||
@@ -1,4 +1,12 @@
|
||||
module.exports = {
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: { "type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]] },
|
||||
defaultIgnores: false,
|
||||
ignores: [
|
||||
message => /^chore\(release\): \d+\.\d+\.\d+$/.test(message.trim()),
|
||||
message => /^Merge pull request ['"].+['"] \(#\d+\) from .+ into .+$/.test(message.trim()),
|
||||
],
|
||||
rules: {
|
||||
"type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]],
|
||||
"references-empty": [2, "never"],
|
||||
},
|
||||
};
|
||||
|
||||
Generated
+221
-654
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "wealth-ledger-workspace",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "24.21.0"
|
||||
"node": "24.22.1"
|
||||
},
|
||||
"scripts": {
|
||||
"release:preview": "release-it --dry-run --ci",
|
||||
@@ -11,16 +11,16 @@
|
||||
"commitlint": "commitlint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "21.2.2",
|
||||
"@commitlint/config-conventional": "21.2.2",
|
||||
"@release-it/conventional-changelog": "12.0.0",
|
||||
"conventional-changelog-conventionalcommits": "10.4.0",
|
||||
"release-it": "21.0.2"
|
||||
"@commitlint/cli": "21.2.4",
|
||||
"@commitlint/config-conventional": "21.2.4",
|
||||
"@release-it/conventional-changelog": "12.0.2",
|
||||
"conventional-changelog-conventionalcommits": "10.4.1",
|
||||
"release-it": "21.1.1"
|
||||
},
|
||||
"overrides": {
|
||||
"basic-ftp": "6.2.2",
|
||||
"fast-uri": "3.1.8",
|
||||
"ip-address": "10.7.3",
|
||||
"undici": "7.29.1"
|
||||
"basic-ftp": "6.2.3",
|
||||
"fast-uri": "3.1.9",
|
||||
"ip-address": "10.7.4",
|
||||
"undici": "7.30.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user