Files
main/audits/2026-10-11/reports/10-custom-scripts-and-tooling.md
codex-bot d07fbfabb1 docs: archive technical audit 2026-10-11 in develop (#36)
Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
2026-10-17 19:38:03 +03:00

505 lines
79 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Аудит 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 |