Files
main/audits/2026-10-11/reports/11-validation-and-ci-cd-efficiency-report.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

68 KiB
Raw Permalink Blame History

Аудит 11 — Validation & CI/CD Efficiency

1. Audit metadata

  • Audit ID/name: 11 / Validation & CI/CD Efficiency.
  • Дата: 2026-10-11.
  • Режим: read-only для application/specs/docs repos; единственная запись — этот отчёт в родительском workspace.
  • Область: execution DAG проверок, triggers, независимые CI guarantees, повторная подготовка, staged hooks, генерация контрактов и граница image publication. Алгоритмы самописных инструментов, assertions тестов, dependency security и поведение агента не аудировались.
  • Skill: установленный ponytail-audit прочитан через Skill tool и применён к complexity-pass. Его ограничения сохранены: correctness findings ниже — результат протокола аудита 11, а не вывод skill. Дополнительно прочитан verification-before-completion для контроля итогового артефакта.
  • Предыдущий аудит: audit-results/technical-audit-2026-10-11.md прочитан как реестр гипотез. Его измерения dependency graph, совпадения OpenAPI и выводы о тестах не использованы как актуальное доказательство.

Workspace: /Users/ksv741/Project/vibe/WealthLedger. Это родительская папка самостоятельных Git-репозиториев:

Repo Найденный путь относительно workspace HEAD SHA Ветка Начальный git status --short
frontend frontend/ c8821200ce699dab1ce65360d1968d5877a2e78b codex/fix-stock-screener-race clean
backend backend/ 0ad84ff4f0b4712abc7e69dc299856f14af6eadd codex/add-stock-screener clean
specs specs/ fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121 codex/add-stock-screener clean
docs docs/ e6fd4f64b81dd73b33959b845a1babcfac1891c8 codex/add-stock-screener clean
workspace root, вспомогательная область ./ dc030f84c979cedb2628ef4bd97c62d6c63a9256 release/0.2.1 clean

Также обнаружен отдельный deploy/.git; его исходники и pipeline не исследовались. Downstream deployment описан только по доступным root Compose и docs/architecture/deployment.md. Наличие локального checkout не доказывает production state.

Что выполнялось

  1. Статическое чтение repo instructions, manifests, workflows, hooks, compiler/test/build configs и вызываемых entry points; файловый inventory через Read/Glob/Grep.
  2. В каждом из четырёх repos и root: git status --short --branch, git rev-parse HEAD.
  3. В четырёх repos: read-only git config --get core.hooksPath. Frontend: .husky/_; specs: .githooks; backend/docs: значение отсутствует, команда вернула 1 без вывода. Это отсутствие настройки, а не ошибка проверки приложения.
  4. В specs: git diff --check && git diff --stat && git diff --cached --stat — exit 0, вывода нет; проверены пустые локальные diffs, не PR content.
  5. Через Context7 прочитана актуальная документация Gitea о PR activity types и head ref: Actions FAQ, webhook payload. Внешний источник подтверждает поддержку edited; версия и настройки реального Gitea не запрашивались.
  6. После записи отчёта: повторный read-only контроль Git status/HEAD всех пяти областей — SHA не изменились, working trees clean. git diff --check root — без diagnostics. No-index whitespace-check отчёта относительно /dev/null — без diagnostics; exit 1 обозначает наличие нового содержимого относительно пустого файла. Root git status --short --ignored -- <report> показывает игнорируемый audit-results/, согласно allowlist; отчёт не добавлялся в tracking.

Не выполнялось: npm/Node/OpenSpec команды, install, lifecycle hooks, tests, lint, build, full verify, Prisma/OpenAPI codegen, Docker, migrations, обращения к брокерским/банковским API, npm audit, чтение secrets, commit/PR, изменение source/config/lockfiles/generated artifacts. CI runs/logs и branch protection settings не запрашивались. Тайминги отсутствуют; все оценки экономии — not measured.

2. Executive verdict

  1. Факт / FIX: specs verify в CI проверяет разницу working tree с index, а не проверяемые commits. В чистом checkout это пустая проверка, хотя её статус является задуманным required check. Самый дешёвый полезный gate — whitespace-check явного диапазона commits без Node installation в этом job. VERIFY-001.
  2. Факт / риск: четыре repos исключают PR edited из triggers commitlint. Заголовок можно изменить после зелёного статуса без нового запуска; frontend/backend README обещают обратное. VERIFY-002.
  3. Факт / SIMPLIFY: backend non-scheduled verify job содержит 3 явных Prisma generation и 2 Nest build. При этом standalone verify генерирует client слишком поздно для чистого checkout. Нужен один owner подготовки до потребителей типов и reuse той же сборки в e2e, а не механическое удаление первого generate. VERIFY-003.
  4. Факт / SIMPLIFY: frontend pre-commit после staged lint каждый раз собирает два private packages и сканирует весь dependency graph, даже для Markdown-only commit. Полный граф проверяется также в final verify и CI. Обязательные security/staged lint можно сохранить, а full graph оставить composed gates. VERIFY-004.
  5. KEEP: CI уже избегает дублирующего feature-branch push-run, имеет fail-fast цепочку commits → security → verify, отмену устаревших ревизий и вынесенный тяжёлый performance benchmark. Unit/SQLite, JSDOM/Chromium и два dependency graphs имеют разные гарантии; объединять их по похожим именам нельзя.
  6. UNVERIFIED: фактические CI minutes, npm/browser/build cache hits, версия Gitea, действующие required checks и защита устаревшего PR head от нового base не проверены. Это вопросы для короткого просмотра существующего run и настроек, а не основание отключать gates.

Итог: четыре подтверждённых P2 findings, без подтверждённых P0/P1. Основная экономия достигается сокращением повторной подготовки и правильным input scope дешёвых gates; расширение количества валидаторов не требуется.

3. Inventory & coverage

3.1 Изученные объекты и метод подсчёта

Объект Охват и метод Результат
CI definitions Полное чтение всех файлов в .gitea/workflows/ четырёх repos и root 11 workflow files, 15 job definitions; сумма: frontend 2/4, backend 2/4, specs 3/3, docs 3/3, root 1/1
CI platforms Все перечисленные job definitions и .nvmrc Один label ubuntu-latest, нет OS/Node matrix; все .nvmrc — 24.21.0
Local commands Пять root manifests, три frontend workspace manifests Раскрыты verify, standalone checks, build, generation, release entry points и root lifecycle
Git hooks Tracked hook files, actual hooksPath, backend/docs .git/hooks, Husky launcher Frontend: pre-commit + commit-msg; specs: commit-msg; backend/docs: только .sample; активных pre-push checks не найдено
Backend e2e selection Glob backend/test/*.e2e-spec.ts + test/sqlite.cjs:7–17 20 suite files; --all выбирает 19 functional suites, исключая один performance suite; это число файлов, не assertions
Browser selection Glob *.browser.test.tsx в frontend src/packages + vitest.config.ts:36–64 2 файла, каждый включён для narrow/wide Chromium instances: 4 configured file-instance combinations без фактического запуска
Contracts Backend generator + runtime integration suite; frontend check/generate Прослежены источник, временный output, byte comparison, freshness и consumers
Downstream artifacts Dockerfiles, image workflows, coverage/Sonar configs, docs deployment Build provenance boundary и consumers покрыты статически; registry/deploy execution не проверены

Counts относятся только к найденным локальным checkout, не к server-wide jobs/runners. Явные повторы команд считались разворачиванием manifest chains и workflow steps на успешном пути; lifecycle сторонних packages и cache-hit effects не выдаются за измеренную длительность.

Из installed manifests отдельно проверены backend/node_modules/@prisma/client/package.json:264–275 и backend/node_modules/prisma/package.json:194–205: у этой версии client нет postinstall generation, Prisma имеет preinstall. Это поддерживает гипотезу cold-checkout failure standalone verify; установленное дерево не заменяет воспроизведение fresh install.

3.2 Исследовательский порядок и исключённые гипотезы

  1. Inventory entry points → выбор рисков stale/no-op proof и повторной подготовки.
  2. Tracing workflow → package script → runner/generator/build outputs → consumer.
  3. Проверка альтернатив: отдельный front generate нужен до типов; standalone wrappers обеспечивают свежесть packages; graph modes отличаются; OpenAPI delivery явно ручная; image publication не равна server rollout.
  4. Сформированы четыре findings; без искусственного расширения до всех возможных оптимизаций.

Не аудировались assertions и качество coverage, алгоритмы custom scripts, advisories/overrides, OpenSpec schema/lifecycle design, глобальные editor/agent hooks и отдельный deploy repo. Исторические archives не использованы как proof текущих triggers.

4. Check Execution DAG

Ниже фактическая конфигурация. Обязательное локальное действие из conventions отмечено отдельно от автоматического trigger.

4.1 Edit/save и ручная проверка

developer / agent edits
  ├─ frontend npm run dev [только если процесс запущен]
  │    └─ build:packages → Vite/HMR на изменениях
  ├─ frontend npm run dev:catalog [только если процесс запущен]
  │    └─ build:packages → Storybook dev :6006
  ├─ frontend npm test [только если вручную выбран watch]
  │    └─ build:packages → Vitest watch
  ├─ backend start:dev/debug [только если запущен]
  │    └─ Nest watch → dist-dev; это не full test gate
  └─ manual checks [решение автора]
       ├─ frontend targeted test / typecheck / lint / deps
       │    └─ каждый standalone wrapper сначала build:packages
       ├─ backend targeted Jest / typecheck / isolated SQLite
       └─ feature complete [локальное требование engineering:60–62]
            ├─ npm ci → root/dependency lifecycle
            ├─ frontend verify
            └─ backend verify + risk-specific e2e/contract/durability

Автоматического full verify на каждый save в просмотренных application configs нет. Vite transform/HMR не доказывает typecheck, test coverage или production bundling. Backend dev compilation не заменяет configured HTTP/DB suites. Все global editor integrations остаются вне этого inventory.

4.2 Раскрытые gates

frontend verify [всё через &&, stop на первой ошибке]
  build:packages
    ├─ icons: vite build → tsc declarations
    └─ ui:    vite build → tsc declarations
    tokens: JSON export, build script отсутствует
  → openapi:check [temp generation + byte compare types]
  → tsc -b --noEmit
  → eslint . --max-warnings=0
  → test:lint [node:test]
  → vitest run --coverage [JSDOM + Chromium narrow/wide]
  → knip
  → depcruise base
  → depcruise Storybook, reachability/runtime graph
  → tsc -b
  → vite build [app/PWA dist]
  → storybook build [storybook-static]

backend verify [всё через &&]
  lint → test:lint → typecheck:app → typecheck:test
  → test:cov --runInBand [Jest в src]
  → build [prisma generate → nest build]

backend test:e2e
  build [prisma generate → nest build]
  → sqlite.cjs --all
       для каждой functional suite последовательно:
       temp DB → штатные migrate deploy → Jest --runInBand → cleanup

backend test:openapi-contract
  build → sqlite.cjs openapi-contract.e2e-spec.ts
       configured app schema ↔ generator from dist in temporary cwd

docs verify
  Markdown lint + relative links/anchors
  → отдельный CHANGELOG lint profile → git diff --check

specs verify
  git diff --check [working tree ↔ index, не PR range]

Sources: frontend/package.json:9–38, workspace manifests, backend/package.json:11–38,123–138, backend/test/sqlite.cjs:7–17,57–95, docs/package.json:8–13, specs/package.json:8–14.

4.3 Commit/push/PR

git commit frontend [actual hooksPath .husky/_]
  Husky sh -e
  → staged Gitleaks, fail closed + redaction
  → lint-staged → ESLint --fix [selected staged JS/TS]
  → npm run deps → build:packages → full working-tree graph
  → commit-msg → commitlint

git commit specs
  commit-msg → commitlint --strict

git commit backend/docs
  application hooks отсутствуют в просмотренном checkout

git push feature branch
  pre-push application checks отсутствуют
  ├─ без открытого PR: verify workflow не запускается
  └─ с открытым PR: synchronize → PR workflow

PR opened / synchronize / reopened
  frontend/backend:
    commits [checkout head, npm ci --ignore-scripts, title + commit range]
    → security [full-history Gitleaks + npm audit]
    → verify [fresh npm ci; подготовка; full gates]
    → Sonar только для develop push, не PR
  docs/specs:
    commits ∥ security ∥ verify [независимые workflows]
  root:
    commits, включая edited [только root PR]

PR edited
  root commits запускается
  четыре repo commitlint workflows не подписаны

push develop/main
  frontend/backend: commits skipped → security → verify
  docs/specs: security ∥ verify; commits отсутствует

daily schedule
  frontend/backend/specs/docs: security only

Frontend/backend PR branch не имеет отдельного push-run: это уже полезная защита от двойного CI для одного feature push. Push после merge проверяет новое состояние интеграционной ветки, а не автоматически бесполезно повторяет PR trust boundary.

В Gitea PR ref указывает на head, не на synthetic merge commit — это подтверждено Actions FAQ. Отсутствие merge preview нельзя компенсировать предположением, что зелёный PR автоматически проверен с новым base. Actual stale-branch protection — UNVERIFIED.

4.4 Build/publication/deploy

release:prepare [manual, release/*]
  release-it guards + check-release
  → version/lock/changelog commit; tag/push disabled
manual release PR + protected merge + tag vX.Y.Z
  → component image.yml
       version + HEAD/revision validation
       → npm ci → canonical verify [backend также e2e]
       → registry login → immutable tag absence check
       → Docker production build/push
       → pull exact digest → labels + container smoke
       → cleanup registry session [always]
  → explicit operator selection of component tags / deploy bundle
  → rollout [отдельный repo, здесь не проверялся]

Docker заново выполняет installation/build внутри packaging environment. Это другая гарантия: чистый Linux image с нужными native modules/assets, а не повтор host typecheck ради того же proof. Backend дополнительно имеет повтор генерации внутри Docker stages — VERIFY-003.

Container smoke сейчас идёт после push, поэтому его failure не отменяет publication. Это не automatic deploy: docs/architecture/deployment.md:76–85,132–135 явно требует отдельно проверить artifact и выбрать bundle, запрещает удалять tag ради retry. Backend workflow отказывается повторно публиковать существующий tag; frontend умеет проверить существующий digest. Без доказательства опасного rollout это не отдельный дефект аудита; immutable guards и operator smoke — KEEP.

5. Guarantee-to-Check Matrix

Для всех rows expected runtime = not measured. Timeout job — верхняя граница ожидания, а не measured runtime. R = read-only для versioned inputs; W = writes outputs/index/config. Даже R-check может иметь временные cache files.

Check Guarantee Trigger / input scope Read/write behavior Downstream consumer
npm ci Manifest/lock reproducible installation, не semantic proof Conventions completion; CI install; все lockfile dependencies W: node_modules; lifecycle, frontend Husky/specs hooks config Все Node checks; Docker dependencies
Frontend build:packages JS/CSS/declarations для package exports актуальны Standalone wrappers, verify, dev/catalog/build; icons/ui целиком W: package dist TS, ESLint resolver, Vitest, app/Storybook
Frontend openapi:check Committed schema.d.ts байтово соответствует генерации из принятого snapshot Full verify; snapshot + generator version + generated file R tracked; W private temp + cleanup Frontend typed API client
Backend openapi:generate Artifact построен из текущего compiled backend Manual delivery; current DTO/Swagger + dist W: root ignored openapi.json; сначала build Явно передаваемый consumer artifact
Backend OpenAPI integration Generator output from current build равен schema configured current app; generator не открывает сеть Contract change; CI e2e --all W private DB/temp JSON; не versioned snapshot Публичный generator и HTTP contract
Frontend tsc -b, с/без CLI --noEmit Repo-wide strict TS configs и test/story/package compilation typecheck, verify, app build; referenced projects W buildinfo; app/node configs имеют noEmit Локальный proof типов, bundle gates
Backend typecheck app/test Production roots и test compilation verify / manual; app subset и весь src+test R, incremental:false/noEmit:true Unit/e2e compilation confidence
Full ESLint + config tests Configured rules, zero-warning policy, validation profile verify/standalone; repo JS/TS R для check; lint:fix отдельно W Code review и next gates
lint-staged Lint selected staged JS/TS + fixes Frontend commit W: selected content/index, возможный stash Commit contents; не full-repo proof
Frontend Vitest coverage Unit/transport/UI + actual layout checks; configured coverage floors verify/coverage; full configured projects W: coverage HTML/lcov и test artifacts Merge gate, Sonar lcov
Backend Jest coverage Colocated src unit suite проходит verify/test:cov; src specs W: coverage; общего threshold нет Merge gate, Sonar lcov
SQLite functional runner Configured HTTP/DB suites на isolated fresh migrations test:e2e, CI; 19 выбранных suite files W: temp DB only, cleanup Authorization, transaction/CAS/replay/contract proof
test:sqlite Auth/cards subset, не все HTTP suites Manual risk-based iteration; default 2 suite files W isolated DB; требует заранее generated client Быстрая DB regression feedback
Base dependency-cruiser Cycles, resolution, public API и layer boundaries deps/verify/hook; весь src+packages, TS deps R graph; wrapper W package build Architecture gate
Storybook dependency-cruiser Runtime reachability stories/preview не доходит до data/auth/routes deps:storybook/verify; graph + preview, tsPreCompilationDeps:false R graph; wrapper W package build Независимость UI catalog
Knip Unused exports/dependencies с repo entry point policy verify/standalone; all workspaces R analysis, wrapper W package build Maintenance feedback
Vite/Storybook builds App/PWA и catalog действительно bundle verify/build; разные roots/plugins W dist/storybook-static Docker app output / UI catalog
Commitlint title + range Заголовок и выбранные commits Conventional Commits PR events; title + BASE/HEAD R; job installation W Required commits status; release history
Gitleaks Staged/history secret scan + redaction/fail closed Frontend commit index; CI full available history, daily R inputs; CI downloads scanner; temp hook test Commit/merge security boundary
npm audit Registry advisory policy проходит для выбранных trees/levels PR/push/daily security Network read + возможные local logs; lock unchanged Required security status
Docs Markdown Syntax + local Markdown links/anchors, включая новые файлы verify/CI; all Markdown + отдельный CHANGELOG R Docs consistency; не Mermaid/semantic proof
Specs/docs git diff --check Whitespace errors в незакоммиченном working-tree diff local verify, также current CI commands R; untracked/index-only/committed changes не охвачены Локальная проверка; в clean CI inputs пусты
Docker production build + smoke Packaging, native runtime/assets/migrations и startup проходят Release tag; image environment и pulled digest W image/registry/disposable volumes Explicit deployment selection
Sonar Quality Gate Remote analyzer принял source и existing unit lcov Только develop push после gates W remote analysis Develop push status, не PR proof
Durability / performance / runner regression Migration/restart/backup proof либо load/runner behavior Manual risk checks; не ordinary verify W disposable outputs/DB; regression многократно запускает e2e Schema-change proof / dedicated investigation

OpenAPI: четыре разных утверждения

  1. Backend file equals frontend snapshot: сравнение двух сохранённых JSON доказало бы лишь их текущее совпадение. В этом аудите оно не выполнялось.
  2. Backend generator freshness: test/openapi-contract.e2e-spec.ts:108–158 вызывает generator from fresh dist в temporary cwd и сравнивает его JSON с createOpenApiDocument(app) current source/configured app. Это реально покрывается CI e2e.
  3. Frontend type freshness: scripts/check-openapi.mjs:10–38 генерирует из frontend-local accepted snapshot и сравнивает bytes schema.d.ts; backend freshness из этого не следует.
  4. Release compatibility / latest delivery: предыдущий release и предоставленный frontend artifact — другие inputs. Backend не имеет versioned JSON baseline (backend/.gitignore:69–70), delivery описана явно (backend/README.md:218–228, frontend/README.md:201–204). Эти guarantees не были заявлены existing check и здесь остаются unverified.

KEEP: independent producer/consumer checks и offline generator guard. Не добавлять обязательный sibling checkout или второй editable contract source.

6. Duplicate Execution Matrix

6.1 Check × triggering context

Это configured occurrences на успешном пути, не частота реальных действий. F = один frontend full verify; B = один backend full verify. Host и Docker считаются раздельно.

Check Frontend targeted wrapper F Frontend pre-commit Frontend CI verify job Backend targeted contract B Backend CI verify job: pre-step + B + e2e Release image job
Frontend package build 1 для test/type/lint/deps/knip 1 1 всегда после lint-staged 1 — — — 1 host F + 1 Docker app build при cold build
Frontend repo tsc build mode 0 или 1, по wrapper 2 invocations 0 2 — — — F + Docker type/build path
Frontend base depcruise 0 или 1 1 1 1 — — — 1 host F
Storybook graph 0 или 1 1 0 1 — — — 1 host F
Vitest target / full coverage Targeted subset 1 full 0 1 full — — — 1 host full
Prisma generation, explicit — — — — 1 1, поздно 3 3 host + 2 Docker stage path при cold backend image build
Nest build — — — — 1 1 2 2 host + 1 Docker
Backend unit coverage — — — — 0 1 1 1 host
OpenAPI integration — — — — 1 suite 0 1 suite внутри --all 1 suite внутри --all
Backend functional SQLite suites — — — — 1 suite 0 19 suite files 19 host suite files

Frontend два tsc invocations не равны двум полным recompilations: referenced configs уже noEmit, используются buildinfo. Ускорение от удаления второго не измерено. Backend app/test typecheck имеют перекрытие src, но разные program roots; удаление app-only proof без проверки ambient test effects не рекомендовано.

6.2 Матрица трёх событий

Событие Возможный локальный путь Commit Push / PR Где повтор действительно лишний / где оправдан
(a) Одна UI правка в frontend HMR, optional test:run -- <path> → по completion policy npm ci + F Staged Gitleaks + ESLint fixes + full deps с package build Feature push: только PR synchronize, если PR открыт; CI fresh install + F Targeted test и F дают разные scope/coverage; CI — independent boundary. Hook повторяет package build/base graph после F; для Markdown этот же hook не имеет relevant graph input
(b) Backend contract change Targeted HTTP/contract; test:openapi-contract уже build+suite → B + relevant e2e; generator delivery отдельно Backend application hook нет PR: commits → security → pre-generate + B + build/e2e Отдельный contract command и --all могут повторить ту же suite; в одной неизменённой local revision повторный targeted run не нужен. В CI 3 generate/2 builds обслуживают одну revision и environment
(c) git push / PR Push сам не вызывает local verify pre-push checks нет Feature без PR: pipeline нет. PR open/update: full gates. Merge push develop/main: integration gates. Release tag: image pipeline PR/branch/tag stages имеют отдельные inputs и trust boundaries. Не удалять tag verification без same-SHA/lock/environment gated proof. Изменение только PR title сейчас не вызывает новый commits check

UI изменение не запускает backend/specs/docs checks автоматически: это независимые repositories. Backend artifact change требует проверки frontend только при явном изменении принятого frontend input, а не обязательного просмотра соседнего repo для любой backend правки.

7. Findings

VERIFY-001 — Specs CI verify не проверяет проверяемый commit range

  • Decision = FIX; Severity = P2; Confidence = High; Effort = S.
  • Evidence: specs/package.json:8–14; specs/.gitea/workflows/verify.yml:13–22; specs/README.md:33–35,52–55,68–70. Для сравнения настоящее content-check в docs: docs/package.json:8–10, docs/.gitea/workflows/verify.yml:23–25.
  • Claim: required-by-intent specs verify в clean CI checkout не проверяет whitespace committed PR/push content. Node setup/install обслуживает фактически пустой worktree diff.
  • Current mechanism: workflow checkout → setup-node → npm ci → git diff --check; перед этой командой нет изменения tracked content и не передаются BASE/HEAD. prepare меняет hooksPath, не рабочие Markdown inputs.
  • Consumer: PR required verify из README; локальный автор использует тот же script. Локально команда полезна для unstaged tracked diff, но не охватывает уже staged changes и новые untracked files.
  • Impact / actual cost: ошибки whitespace в committed change проходят этот job; ложная уверенность в проверке diff. Отдельные setup/install steps не дают content guarantee. Impact unmeasured: реальный пропущенный PR и CI minutes не наблюдались.
  • Why now: это можно исправить существующим Git check, не вводя новую validation framework и не изменяя ответственность specs.
  • Minimum change: оставить локальный script локальной проверкой либо явно охватить staged diff; в CI выбрать содержательный commit range и вызвать Git напрямую. Для PR обеспечить наличие base/head commits, проверить merge-base → head; для push — before → after, с явно определённым fallback для creation/zero SHA. Только после этого убрать setup-node/npm ci из verify job; commits/security остаются прежними.
  • Guarantees at risk: корректность range и presence of history. Shallow checkout без нужного base даёт неполную проверку; просто удалить job при unchanged required-check settings нельзя.
  • Counterarguments / trade-offs: specs README обещает лишь diff, не native OpenSpec validation. Finding не требует запускать schema/semantic validation на каждый push. Docs verify не является no-op: его Markdown lint действительно читает содержимое, хотя последующий worktree diff там также пустой.
  • Verification or rollback: в будущем PR с trailing whitespace в committed Markdown должен fail, исправленный — pass; проверить staged/local и first-push fallback отдельно. Достаточен disposable test branch/repo, application suite не нужна. До proof оставить существующий required check и затем сохранить его имя. Rollback — вернуть workflow, не менять Git history.
  • Related finding IDs: VERIFY-002; cross-audit 15 только по самостоятельной native OpenSpec policy.

VERIFY-002 — Commitlint не перезапускается при изменении заголовка PR

  • Decision = FIX; Severity = P2; Confidence = High; Effort = S.
  • Evidence: frontend/.gitea/workflows/verify.yml:3–7,17–37; backend/.gitea/workflows/verify.yml:3–7,17–37; docs/.gitea/workflows/commits.yml:3–5,23–30; specs/.gitea/workflows/commits.yml:3–5,23–30; обещание — frontend/README.md:116–120, backend/README.md:80–85; работающий root precedent — .gitea/workflows/commits.yml:3–5,19–26.
  • Claim: все четыре repo workflows фильтруют opened/synchronize/reopened и не обрабатывают edited; title input может измениться без rerun validation, при том же HEAD.
  • Current mechanism: job читает event PR_TITLE и проверяет его вместе с commit range только на перечисленных events. Local commit-msg проверяет commit, а не будущий PR title, и не компенсирует отсутствие trigger.
  • Consumer: required commits status, Conventional Commit policy для PR/release history. Gitea FAQ подтверждает edited activity; отсутствие этого type — конфигурационное, а не отсутствие platform capability.
  • Impact / actual cost: green status относится к прежнему title; после invalid rename policy proof становится stale. Фактический merge с таким title не наблюдался; Impact unmeasured.
  • Why now: несоответствие дешёвого check собственному input trigger устраняется малой правкой; не нужны новые rules.
  • Minimum change: обеспечить commits/title check на edited; docs/specs могут добавить type в отдельный commits workflow. Для frontend/backend сохранить selective dispatch: metadata-only event должен обновить commits proof без нового Chromium/Jest/build run. Не добавлять edited в full workflow без условия, если это просто умножит дорогие gates.
  • Guarantees at risk: текущие names required checks и отношение skipped jobs к имеющимся success statuses. При разделении workflow нельзя случайно сделать старый обязательный context недостижимым или заменить failed verification на skipped success.
  • Counterarguments / trade-offs: title lint — выбранное самим проектом правило, не runtime-security boundary. Его можно отдельно пересмотреть как policy, но workflow сейчас обещает его enforce. Содержание PR body тоже может давать edited; желательно проверять title-change payload, если платформа предоставляет нужные fields, без сложного custom classifier.
  • Verification or rollback: на existing successful PR изменить только title на invalid, убедиться в новом failed commits check; вернуть valid title и получить success без нового verify run. Проверить branch protection contexts и поведение skipped statuses на фактической Gitea. Rollback — trigger/job changes, contexts восстанавливаются согласованно.
  • Related finding IDs: VERIFY-001; остальные guarantees independent.

VERIFY-003 — Backend preparation повторяется и не принадлежит standalone verify целиком

  • Decision = SIMPLIFY; Severity = P2; Confidence = High; Effort = S.
  • Evidence: backend/.gitea/workflows/verify.yml:83–89; backend/.gitea/workflows/image.yml:30–33; backend/package.json:12–13,22–34; backend/.gitignore:7–8; backend/prisma/schema.prisma:1–5; backend/src/prisma/prisma.service.ts:1–5; backend/Dockerfile:8–10,12–22; backend/test/openapi-contract.e2e-spec.ts:139–156.
  • Claim: в одном обычном verify job explicit generation вызывается 3 раза, Nest build 2 раза. Одна revision проверяется без changes между этими шагами. Local standalone verify при отсутствующем ignored client доходит до typechecks до собственной generation.
  • Current mechanism: (1) CI explicit prisma generate; (2) verify заканчивается build = generate + nest build; (3) test:e2e начинает тот же build. Root verify не имеет preparation перед typed lint/typecheck. Installed client manifest этой версии не имеет postinstall generation. Docker production path также наследует direct generate из dependencies stage, затем генерирует в package build ещё раз.
  • Consumer: typed source import generated Prisma, app/test typechecks и unit tests; runtime OpenAPI generator импортирует dist/src/app.module.js. Последний требует свежую сборку, а не просто возможность Jest transpile source.
  • Impact / actual cost: подтверждён лишний запуск двух host generations и одного host Nest build; cold Docker path ещё одного generate. Потенциальный cold-local failure выводится из import/order, но fresh checkout runtime-воспроизведение не выполнялось. Impact unmeasured для latency/CPU; число explicit configured calls измерено статически.
  • Why now: same job, same SHA и одно environment дают возможность reuse без ослабления независимой CI boundary.
  • Minimum change: сделать standalone verify владельцем generation до typed consumers, оставить один Nest build; после successful verify вызвать существующий node test/sqlite.cjs --all без второй сборки. Удалить workflow pre-generate только после переноса подготовки в verify. Сохранить standalone build и test:e2e self-contained. В Docker перенести direct generation в development target, если production build уже гарантирует её: development должен сохранить generated client.
  • Guarantees at risk: свежесть generated client для schema change; наличие current dist для OpenAPI; copied proto assets и native Linux modules; standalone command UX. Кеширование host-generated dist между разными environments не предложено.
  • Counterarguments / trade-offs: первый CI generate необходим сейчас — его удаление само по себе ломает clean typechecks. Второй build необходим standalone test:e2e, но избыточен в composed CI после verify. Build в Docker не удаляется: он доказывает packaging environment. Generation/internal build cache могут уменьшать стоимость повтора, но не устраняют лишний invocation.
  • Verification or rollback: один clean disposable checkout с approved install: standalone verify → existing SQLite --all; лог должен показать 1 explicit host generate и 1 Nest build при тех же 19 suites и runtime schema comparison. Schema change должен влиять на типы и generator. Development и production Docker targets проверить отдельно. Rollback — scripts/workflow/stage ordering; source/data migrations не нужны.
  • Related finding IDs: VERIFY-004, только общая тема подготовки; не объединять repos общим tooling.

VERIFY-004 — Frontend pre-commit всегда делает package build и whole-repo graph

  • Decision = SIMPLIFY; Severity = P2; Confidence = High; Effort = S.
  • Evidence: frontend/.husky/pre-commit:1–3; installed launcher frontend/.husky/_/h:6,14–22; frontend/package.json:25–26,33,36–38; frontend/packages/icons/package.json:9–20; frontend/packages/ui/package.json:9–24; frontend/dependency-cruiser.cjs:24–29,78–86.
  • Claim: каждый successful frontend commit, включая Markdown-only, после staged lint запускает два package builds и full base graph. Команда не зависит от staged input relevance и не повторяет именно staged proof.
  • Current mechanism: actual hooksPath → Husky sh -e → Gitleaks index → lint-staged → npm run deps → build workspaces → depcruise src/packages working tree. Final verify/CI уже включают тот же base graph и отдельный Storybook graph.
  • Consumer: local commit readiness. Граф working tree может блокировать commit из-за незакоммиченного unrelated code; это отличается от selected staged ESLint input.
  • Impact / actual cost: 2 workspace library builds + whole-repo dependency analysis на каждый такой commit, даже при отсутствии changed code/config inputs; после final verify — повтор в той же local revision, если код не менялся. Impact unmeasured: hook duration, частота commits и --no-verify не измерены и не утверждаются.
  • Why now: перенести existing whole-repo proof в existing final/CI boundary проще, чем писать новый staged graph builder.
  • Minimum change: убрать npm run deps из mandatory pre-commit; сохранить full graph в final verify/CI. Сохранить fail-closed staged Gitleaks и staged lint. Если early graph feedback обязательно по принятому workflow, минимально ограничить existing command relevant source/package/TS/resolver/dependency config changes, не считать content-only import diff полноценным incremental graph proof.
  • Guarantees at risk: более поздняя обратная связь о cycles/import boundaries. CI guarantee не теряется при сохранении full graph и required verify; staged lint обязан получать актуальные package declarations при relevant package changes.
  • Counterarguments / trade-offs: standalone deps prep нужна для fresh package exports, поэтому её нельзя убрать глобально из command. Local graph catches errors до отправки и полезен, если runtime мал. Без measured duration рекомендована минимальная смена layer, не сложная cache/fingerprint orchestration.
  • Verification or rollback: будущий Markdown-only commit должен выполнять staged secret scan и commitlint, без package build/graph. Secret fixture должен по-прежнему fail до downstream commands; existing scripts/test-pre-commit.mjs использовать при изменении hook. Import cycle должен fail в full verify/CI. Проверить relevant staged TS lint на clean/fresh declarations. Rollback — вернуть одну hook command.
  • Related finding IDs: VERIFY-003; cross-audit 10 — implementation quality custom hook test, здесь не исследовалась.

8. DELETE / SIMPLIFY / KEEP / INVESTIGATE

Decision Механизм Основание и сохраняемая гарантия
DELETE, условно после FIX Node setup/npm ci в specs verify job После перехода на meaningful Git range Node не consumer; commits/security dependency checks остаются. VERIFY-001
SIMPLIFY Backend host generation/build DAG и Docker duplicate generate Один свежий artifact в каждом environment; не удалять pre-type preparation. VERIFY-003
SIMPLIFY Mandatory full deps в frontend pre-commit Whole-repo graph оставить final + required CI; staged Gitleaks/lint сохранить. VERIFY-004
FIX Specs CI input range; title edited triggers Proof должен зависеть от actual checked input. VERIFY-001/002
KEEP Независимый CI после local checks Local success не trusted proof для merge; install/checks повторяются на новой boundary обоснованно
KEEP SQLite functional suites и temp DB guard Unit mocks не заменяют authorization/CAS/transaction/replay proof; e2e-env отклоняет неisolated DB
KEEP Current-build OpenAPI/runtime comparison и frontend byte check Разные producer/consumer inputs; standalone offline generator guard сохраняется
KEEP Base и Storybook dependency graphs TS graph и runtime reachable graph имеют разные policy/inputs
KEEP JSDOM и narrow/wide Chromium DOM logic не доказывает реальную геометрию; viewports не OS matrix
KEEP Full coverage в final/CI и Sonar lcov Targeted tests без coverage — iteration feedback; не заменяют global coverage floor или report consumer
KEEP Security before expensive verify; fail-closed exits Cheap failures останавливают дорогую работу в пределах DAG; не превращать advisory/secret fail в green
KEEP Feature PR-only verification, event-scoped cancellation, immutable image labels/tags Уже уменьшает redundant runs и защищает artifact identity; фактическая server configuration остаётся unverified
KEEP Performance/durability отдельными commands Wall-clock shared-runner budget отдельно; schema-risk proof не отменять из-за того, что он вне ordinary verify
INVESTIGATE npm/browser cache В workflows не задан cache input/action; persistent runner cache может уже работать. До timing/hit proof не вводить новый cache layer
INVESTIGATE Full app gate для Markdown-only application PR Trigger сейчас whole-repo. Сначала определить affected paths и required-check semantics; не вводить path filters, оставляющие required status pending/skipped
INVESTIGATE Второй frontend tsc, перекрытие backend app/test typecheck Invocation overlap есть; cache и program-root differences не позволяют доказать ценность удаления без diagnostics comparison
INVESTIGATE PR head freshness against updated base Gitea не строит merge preview; actual branch protection не доступна в static checkout

Complexity-pass по фактическому ponytail-audit

  • shrink: сократить повтор host generation/build в backend composed CI до одной подготовки; reuse existing SQLite runner. [backend/package.json:12,28,34; .gitea/workflows/verify.yml:83–89]
  • delete: убрать mandatory whole-repo deps/build из frontend pre-commit; replacement — existing final/CI graph gate. [frontend/.husky/pre-commit:3]
  • native: заменить пустой CI worktree diff + Node installation в specs на Git commit-range check. [specs/.gitea/workflows/verify.yml:18–22]

net: lines/dependencies reduction not measured, подтверждённых кандидатов на удаление dependency нет. Skill заканчивается оценкой net; числовой -N lines здесь не выдумывается, поскольку patch не готовился. Это не proposal удалить checks как таковые.

9. Cost & benefit

Изменение Ожидаемый класс benefit Фактическое основание Обязательный regression proof
Meaningful Git range без Node в specs job CI minutes, maintenance, trust in gate Один setup-node и один npm ci станут unnecessary для Git-only consumer; время not measured Committed whitespace fail, safe range/fetch/fallback, preserved required context
Metadata-only title check Reliability; avoidance of expensive metadata rerun Сейчас edited не обрабатывается; новых минут/сокращения not measured Invalid title fail + valid pass без app rerun; no skipped-status bypass
Backend preparation once CI CPU/minutes и local reproducibility 3 explicit host generate → intended 1; 2 host build → intended 1 на composed path; время not measured Cold checkout/types/schema/dist, same e2e selection, runtime OpenAPI equality
Lean frontend pre-commit Developer waiting, cognitive load Один unconditional package-build/full-graph chain можно снять с hook; время/частота not measured Secrets remain fail closed, staged lint meaningful, cycle caught at required CI
Iteration without full coverage/build Local runtime и feedback latency Existing targeted commands уже есть; usage frequency/agent tokens not measured Whole-repo final/CI и relevant risk tests остаются
Optional download caching Potential network/CI minutes Workflow cache config отсутствует; actual hit/miss state UNVERIFIED Same npm ci + lock integrity; Linux native rebuild; Gitea cache compatibility

Ни runtime savings приложения, ни процентов ускорения, ни сокращения agent tokens не измерено. Уменьшение числа explicit commands не равно пропорциональному уменьшению времени.

Это рекомендация, не изменённые инструкции. Сейчас оба engineering-файла требуют npm ci и full verify при code/dependency completion; audit read-only не отменяет это правило задним числом.

Layer Frontend Backend Specs/docs/root Guarantees retained
edit HMR + relevant test:run -- <path> без coverage; targeted ESLint; package preparation при первом запуске/изменении packages Relevant Jest pattern; configured HTTP/SQLite subset при boundary changes; current generated client до typed checks Изменённый текст/anchors; локальный diff, staged отдельно Быстрый observable feedback; money/auth/CAS cases по затронутому риску
feature complete Один full verify после последнего relevant input change; targeted visual/a11y proof Один full verify с self-contained preparation + relevant HTTP/DB suites; contract proof при wire change; durability при schema/data change Docs full Markdown/link gate; specs meaningful diff + native validation при requirements/schema change; root config/text checks Full types/lint/coverage/architecture/bundle и risk-specific proof
pre-commit Fail-closed staged secrets → staged lint/fix → commit-msg; full graph оставить final/CI Existing policy без новых integration hooks Specs commit-msg; root/docs не добавлять full app hook Проверяется actual commit input; fix не выдаётся за read-only proof
CI Fresh lock installation → full verify + Chromium; full graph и coverage floors; security и commits required Fresh generated client → full verify → existing isolated SQLite --all без повторного build; commits/security required Git-range specs check; full docs links; title on edited; root commitlint Independent merge proof; whole-repo cross-consumer regressions; no ignored errors
scheduled Existing daily security; deep suite только при обоснованном риске Existing daily security; performance на dedicated/manual environment; runner-regression не каждый PR Existing daily security; native/store checks по owned risk, не новый обязательный blanket process Advisory drift ловится без edit; load noise не блокирует functional gate

Installation локально следует привязать к manifest/lock/Node/install-state change, а не каждой source правке; в CI сохранить воспроизводимую installation. Это часть общей local/agent policy — см. cross-audit 14, без повторного отдельного finding.

Что можно проверять адресно, что нельзя считать полным proof

Check Safe targeted use Что incremental selection пропускает
ESLint Selected files с тем же typed config и fresh declarations Новые config/rule effects на untouched files; import/type changes у consumers
Jest/Vitest Relevant suite/pattern/project без coverage during iteration Shared helper/provider/transport регрессии в других suites, global coverage floors
SQLite Existing runner с explicit affected suite Другие access/transaction consumers, shared migration/schema regressions; schema change требует broad DB proof
Typecheck Existing project boundary, не случайный список changed .ts Reverse dependencies и public type consumers; production isolation при смешивании test roots
dependency-cruiser Можно выбирать момент full scan по relevant input paths Whole-graph cycles/reachability/resolver effects; changed files only не эквивалент full graph
Knip Full at completion/CI, optional during iteration Reverse references, config-discovered entries и dependency usage across workspaces
OpenAPI Producer current-build proof; consumer provided snapshot proof Breaking changes against old release и delivery freshness вне explicit input set
Markdown relative links Changed source + incoming references при локальном переносе Удаление target ломает unchanged incoming links; final docs full check сохраняется
App/Storybook/image build Local selective build для affected artifact Shared package/theme/plugins влияют на оба frontend consumers; image native/assets не доказаны host build

Fail-fast, parallelism и resources

  • Frontend/backend jobs действительно выстроены по needs; failed commits/security не запускают expensive verify. Daily event не запускает verify. Внутри scripts && останавливает downstream checks.
  • Frontend pre-commit использует sh -e; explicit Gitleaks || exit $? передаёт failure, а не маскирует его. Temporary OpenAPI checks и SQLite runner устанавливают ненулевой exit code при failure. continue-on-error/blanket || true в прочитанных workflows отсутствуют.
  • Docs/specs jobs независимы и могут занимать разные runners одновременно. Последовательность одного application DAG не ограничивает server-wide capacity; README указывает настройку runner.capacity: 1 вне repo. Actual capacity — unverified.
  • SQLite runner уже даёт каждой suite отдельный temp DB и запускает её --runInBand; suites с explicit listen используют port 0. Это уменьшает DB/port collisions, но не доказывает безопасность полной параллелизации всех suites. Общий generated client/dist и CPU/memory остаются shared resources.
  • KEEP serial default: без timings не добавлять Jest/SQLite/Chromium параллельные jobs. Не выполнять одновременно повторные builds, которые перезаписывают одни outputs. Unit/static parallelism допустим только после single preparation и доказанного resource isolation, если measured critical path это оправдывает.
  • Chromium install сейчас идёт до full verify, то есть до cheap type/lint failures. Возможен более ранний static stage, но новый составной gate/повтор package build требует cost proof; это не подтверждённый отдельный finding.

Explicit guarantees retained

  1. Денежная точность и boundary regressions остаются в existing meaningful unit/HTTP/DB checks; audit не предлагает заменить их coverage percentage.
  2. Real SQLite authorization, transactions/rollback, CAS, replay/idempotency и connection-isolation proof остаются CI functional suites; DB никогда не заменяется пользовательской.
  3. Generated client и current dist свежи до своих consumers; runtime contract сравнивается с current generator без внешней сети.
  4. Frontend accepted snapshot/type equality, full types и reverse-consumer proof остаются локальными и independent от sibling checkout.
  5. Full graph cycles/public API/reachability, Chromium layout и app/catalog packaging остаются final/CI checks.
  6. Staged secrets/history security не bypass; failures не становятся green; required contexts не ослабляются через skipped jobs.
  7. Release SHA/version identity, immutable tags, Linux native/assets, post-publication digest smoke и explicit rollout selection остаются.
  8. Durability при data/schema change и controlled lifecycle regression tests не исключаются из risk-based verification из-за их стоимости; содержательность assertions — отдельный аудит 08.

11. Top actions

Quick wins: подтверждённые механизмы, малый diff

Rank Действие Value / risk / effort Finding
1 Сделать specs CI whitespace-check явного commit range; затем убрать Node install из этого Git-only job Убирает no-op proof и лишнюю installation; риск неверного range покрывается tiny fixture; S VERIFY-001
2 Добавить PR title edit coverage, сохранив selective metadata-only execution и проверив contexts Исправляет stale check без full pipeline rerun; S VERIFY-002
3 Свести backend host preparation к одному generate/build на revision, сохранив clean standalone verify и existing e2e runner Подтверждённый same-job повтор; cold-checkout/schema/dist proof обязателен; S VERIFY-003
4 Снять unconditional full graph/build с frontend pre-commit; оставить staged security/lint и required final/CI graph Улучшает каждый commit path без нового tooling; риск только более поздней обратной связи; S VERIFY-004

Решения, требующие тестового подтверждения

Rank Действие Необходимое подтверждение Finding
5 Устранить второй generate в backend production Docker path, оставив его development target Обе Docker targets сохраняют current client, native modules/assets и runtime smoke VERIFY-003

Новое ADR для перестановки уже существующих checks не требуется. Новая policy удаления обязательного gate или изменения public delivery/merge trust boundary потребует отдельного согласованного решения; такой migration здесь не предлагается. Cache tuning и удаление перекрывающихся typechecks не входят в confirmed top actions.

12. Cross-audit signals

  • Audit 10 — custom tooling: frontend/scripts/check-image-publication.cjs, scripts/test-pre-commit.mjs, backend runner-regression/durability entry points; отдельно проверить maintenance/consumer ownership, без ревью их алгоритмов здесь.
  • Audit 14 — agent/local workflow: frontend/.ai/conventions/engineering.md:60–62, backend/.ai/conventions/engineering.md:60–62; mandatory npm ci + full completion gate и session повторяемость относятся к policy использования commands; реальные session counts здесь не измерены.
  • Audit 08 — test intent: backend/test/openapi-contract.e2e-spec.ts, SQLite functional suites и frontend browser tests; этот аудит подтверждает selection/trigger, не достаточность assertions.
  • Audit 12 — dependency/security policy: backend/.gitea/workflows/verify.yml:62–65; разные audit levels и advisory option требуют dependency-policy проверки, не переноса network audit на data edit.
  • Audit 15 — specs/docs: specs/README.md:18–35, specs/.gitea/workflows/verify.yml; необходимость и version ownership native OpenSpec validation — отдельно от подтверждённого no-op Git range.

13. Open questions / verification plan

Вопрос, меняющий решение Статус Самый дешёвый способ подтверждения
Required commits/security/verify contexts действительно обязательны? Проверяется ли outdated base/head? UNVERIFIED: локальный workflow не задаёт server protection Read-only Settings/branch protection export и один existing PR status; не запускать full suite
Фактическая Gitea версия поддерживает concurrency/edited/skipped status behavior как ожидает README? UNVERIFIED для сервера; docs подтверждают edited/head semantics Server version + существующий cancellation log; для VERIFY-002 metadata-only rename на disposable PR
Насколько дорогие npm ci, package build, Chromium install, повтор Nest build? Есть ли persistent cache? not measured Step timestamps одного успешного PR и image run, runner cache/hit logs; после changes сравнить те же steps; не запускать full pipeline ради аудита
Clean standalone backend verify действительно fails до generation? Выводится из import/order; runtime fresh-install proof не выполнялся При реализации VERIFY-003 один disposable clean checkout с normal approved install; проверить до/после. Не использовать локальный generated directory как baseline
Можно ли удалить второй frontend tsc или backend app-only typecheck без потери diagnostics? INVESTIGATE, не top action Compare configured compiler roots/diagnostics на real type regression; удалить только после equivalence proof, не ради invocation count
Нужен ли mandatory local graph до каждого commit, если final/CI его сохраняют? Trade-off VERIFY-004 Выбрать feedback layer у владельца; измерить один existing hook timing. Не писать новый graph-diff/cache инструмент
Есть ли гарантированное подтверждение принятого frontend artifact перед component rollout? UNVERIFIED для deploy repo Read-only release/deployment record с producer SHA/consumer snapshot provenance; separate deployment owner, без обращения к банковским API

Вопросы о benchmark SLAs, существующих business assertions и advisory correctness намеренно не разворачиваются в этом аудите. Отсутствие run/log evidence не означает отсутствие защиты.

14. Машинно-считываемая таблица для консолидации

ID Decision Severity Confidence Repo Files One-line impact Effort Related IDs
VERIFY-001 FIX P2 High specs specs/package.json; specs/.gitea/workflows/verify.yml Clean CI checks empty worktree diff, not committed changes; Node install gives no content proof S VERIFY-002
VERIFY-002 FIX P2 High frontend,backend,specs,docs frontend/.gitea/workflows/verify.yml; backend/.gitea/workflows/verify.yml; specs/.gitea/workflows/commits.yml; docs/.gitea/workflows/commits.yml PR title edits leave previous Conventional Commit status stale S VERIFY-001
VERIFY-003 SIMPLIFY P2 High backend backend/package.json; backend/.gitea/workflows/verify.yml; backend/.gitea/workflows/image.yml; backend/Dockerfile Same CI revision explicitly generates client 3 times and builds twice; standalone preparation occurs late S VERIFY-004
VERIFY-004 SIMPLIFY P2 High frontend frontend/.husky/pre-commit; frontend/package.json Every commit runs package builds and full graph even without relevant staged code S VERIFY-003