# Аудит 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-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 `; 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 |