diff --git a/AGENTS.md b/AGENTS.md index cb5a0c4..ec65fde 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,8 +15,7 @@ Proposal, Delta Specs, Design, Tasks, changes и archives хранятся в `o ## Workflow и verification -Поддерживаемая CLI version задана в `openspec/.cli-version`; новые changes -используют project-local schema `wealth-ledger-sdd`. Артефакты создаются по +Новые changes используют project-local schema `wealth-ledger-sdd`. Артефакты создаются по одному после approval Change Owner. Lifecycle: [`../docs/development/openspec-authority-and-lifecycle.md`](../docs/development/openspec-authority-and-lifecycle.md). @@ -26,20 +25,12 @@ affected areas. Подробный token-efficient routing загружаетс из [`../docs/development/agent-token-efficiency.md`](../docs/development/agent-token-efficiency.md), а не дублируется в schema instructions. -Из workspace canonical pre-archive gate: +Для structural validation из `specs`: ```bash -bash specs/scripts/verify-workspace-change.sh -``` - -Для scoped diagnostics из `specs`: - -```bash -bash scripts/verify-openspec-version.sh openspec schema validate wealth-ledger-sdd --verbose openspec validate --type change --strict --store wealth-ledger-specs openspec validate --specs --strict --store wealth-ledger-specs -bash scripts/verify-openspec-authority.sh ``` Structural validation не заменяет semantic verification и independent read-only review. Tasks отмечаются только после выполненной работы; archive не допускает unchecked tasks или verification override. diff --git a/README.md b/README.md index e98427e..850f1b9 100644 --- a/README.md +++ b/README.md @@ -10,20 +10,12 @@ Design должен оценить `frontend`, `backend`, `specs`, `docs` в Del ## Проверка -Из workspace под Node 22 выполните: +Для native validation из этого репозитория: ```bash -bash specs/scripts/verify-workspace-change.sh -``` - -Для диагностики OpenSpec из этого репозитория: - -```bash -bash scripts/verify-openspec-version.sh openspec schema validate wealth-ledger-sdd --verbose openspec validate --type change --strict --store wealth-ledger-specs openspec validate --specs --strict --store wealth-ledger-specs -bash scripts/verify-openspec-authority.sh ``` Structural validation не заменяет semantic verification и independent read-only review перед archive. Audit-derived работа маршрутизируется через [live remediation registry](../docs/development/audit-remediation.md). diff --git a/openspec/changes/simplify-verification-infrastructure/.openspec.yaml b/openspec/changes/simplify-verification-infrastructure/.openspec.yaml new file mode 100644 index 0000000..2398dbb --- /dev/null +++ b/openspec/changes/simplify-verification-infrastructure/.openspec.yaml @@ -0,0 +1,10 @@ +schema: wealth-ledger-sdd +created: 2026-08-26 +goal: Simplify verification infrastructure by removing redundant project-owned + wrappers while preserving native and risk-based checks. +skip_specs: true +affected_areas: + - frontend + - backend + - specs + - docs diff --git a/openspec/changes/simplify-verification-infrastructure/design.md b/openspec/changes/simplify-verification-infrastructure/design.md new file mode 100644 index 0000000..5e80aa1 --- /dev/null +++ b/openspec/changes/simplify-verification-infrastructure/design.md @@ -0,0 +1,128 @@ +## Context + +Текущий workflow накладывает самописные planning, authority, version и workspace +verifiers поверх native OpenSpec, а repository gates включают runtime preflight, +hygiene-скрипты, meta-tests конфигурации и exact bundle baseline. Workspace +script повторно запускает уже завершённые local gates. Runtime OpenAPI shell +script вручную компилирует и поднимает приложение, хотя проверяемый риск — +согласованность публичного contract — естественно принадлежит integration test. + +## Goals / Non-Goals + +**Goals:** + +- Оставить короткие local gates, где каждая проверка владеет самостоятельным + инженерным инвариантом. +- Использовать native OpenSpec validation для структуры SDD и стандартные + project tools для source, dependency и build quality. +- Сохранить проверку runtime OpenAPI contract через backend integration boundary. +- Исключить повторный запуск full repository gates через workspace dispatcher. + +**Non-Goals:** + +- Не менять application behavior, API document или persisted data. +- Не вводить CI, монорепозиторий, workspace package manager, новую dependency + или новый threshold качества. +- Не переписывать historical archives и Completion Evidence, которые описывают + фактические прежние команды. + +## Decisions + +### Native tools own structural and repository validation + +Удаляются project-owned OpenSpec planning, authority и version scripts, их +fixtures и references. Native OpenSpec commands с explicit canonical store +становятся единственным structural validation; lifecycle approval, semantic +verification, completed tasks и independent review остаются человеческими +archive conditions, а не получают новую автоматическую обёртку. + +Каждый affected repository сохраняет один local `verify` entrypoint, но он +состоит только из непосредственно полезных инструментов: TypeScript, +Biome/ESLint, Vitest/Jest, Knip, dependency-cruiser и build. `engines` и +`.nvmrc` остаются декларацией Node 22; отдельная runtime-проверка не входит в +pipeline. Repository hygiene scripts удаляются: version-control policy остаётся +в `.gitignore`, а scoped diff review обнаруживает material workspace artifacts. + +### Contract verification moves to a Jest integration test + +`verify-runtime-openapi.sh` удаляется. Backend получает focused Jest contract +test, который строит configured Nest application на той же boundary, извлекает +runtime OpenAPI document и сопоставляет его с committed frontend snapshot после +детерминированной canonicalization. Такой test запускается только как required +contract gate change, затрагивающего обе стороны API contract; он не добавляется +к каждому несвязанному backend verification run. + +Frontend сохраняет проверку generated types against committed snapshot. Это +разделяет две разные гарантии: frontend snapshot согласован с generated client, +а snapshot согласован с backend runtime contract. + +### Keep bounded bundle and dependency checks; remove meta-verification + +Dependency-cruiser продолжает проверять actual source graph. Его fixture tests +удаляются, потому что тестируют configuration tool, а не source boundary. +Bundle check сохраняет фиксированный maximum initial payload, но удаляет exact +per-route baseline, baseline update command и тесты собственного measurement +script. Fixed ceiling защищает понятный user-facing limit; exact historical +sizes создают churn без самостоятельного performance contract. + +### Completion is explicit rather than dispatched + +`verify-workspace-change.sh` удаляется. Tasks каждого change перечисляют +affected repository gates, а contract gate указывается только если Design +делает его required. Перед завершением gates выполняются по одному разу в +affected areas; semantic comparison с approved artifacts и independent review +фиксируются отдельно. Documentation, AGENTS, schema instructions and agent +entrypoints описывают эту прямую последовательность без shell dispatcher. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | `affected` | Упрощённый local gate без runtime/hygiene wrappers, meta-tests dependency configuration и exact lazy-route baseline; сохранены quality tools и initial payload ceiling. | +| backend | `affected` | Упрощённый local gate и focused Jest OpenAPI contract integration boundary вместо workspace shell script. | +| specs | `affected` | Native OpenSpec lifecycle и explicit per-area completion заменяют project-owned verifiers and dispatcher. | +| docs | `affected` | As-built workflow, repository entrypoints and verification strategy отражают итоговые direct gates. | + +### Contract Gate Decision + +| Gate | Status | Reason | +| --- | --- | --- | +| runtime-openapi | `N/A` | Change переносит способ проверки, но не изменяет frontend и backend стороны публичного API contract; новый Jest test сохраняет contract gate для будущих API changes. | + +## Solution Assessment + +| Option | Fit | Maintenance and migration cost | Decision | +| --- | --- | --- | --- | +| Retain project-owned wrappers and fixtures | Preserves current checks but repeats native and local work. | High ongoing cost and duplicated failure surface. | Rejected. | +| Native OpenSpec plus direct repository tools | Covers structural validation and source-quality invariants with maintained tools already installed. | Low; documentation and task routing change once. | Chosen. | +| New workflow runner or CI framework | Could centralize orchestration, but adds a platform not required by the stated risk. | High adoption and operating cost. | Rejected. | + +The existing Jest/Nest testing stack is the mature compatible mechanism for the +runtime contract assertion, so no custom process runner or dependency is added. + +## Risks / Trade-offs + +- Removing every-run hygiene scans means an explicitly force-added ignored + artifact is caught by repository review rather than the full verification + pipeline; `.gitignore` remains the normal prevention boundary. +- The OpenAPI test intentionally remains an opt-in contract gate, avoiding an + integration startup for changes that cannot alter the API. +- Fixed bundle ceiling detects material initial-load regression but no longer + blocks harmless byte-level movement among lazy routes. + +## Migration Plan + +1. Replace project-owned wrappers and their references with direct native or + repository-owned checks. +2. Move the runtime OpenAPI assertion to the backend Jest integration boundary + and remove the shell implementation. +3. Simplify package entrypoints while retaining each selected base tool. +4. Update as-built guidance and agent routing, then verify each affected area + once without the removed dispatcher. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | `N/A` | +| Reason | Tooling-only change has no data, auth, security behavior, public API or other difficult-to-reverse runtime decision; reverting affected repository commits restores the previous local workflow. | diff --git a/openspec/changes/simplify-verification-infrastructure/proposal.md b/openspec/changes/simplify-verification-infrastructure/proposal.md new file mode 100644 index 0000000..5b0fe4d --- /dev/null +++ b/openspec/changes/simplify-verification-infrastructure/proposal.md @@ -0,0 +1,35 @@ +## Why + +Проверки change и репозиториев дополнительно проходят через самописные +обёртки, мета-проверки и точные baseline-сравнения. Они дублируют возможности +поддерживаемых инструментов, повторяют tool calls и усложняют обязательный +workflow без защиты самостоятельного продуктового риска. + +## What Changes + +- Упростить verification infrastructure, оставив только проверки, которые + непосредственно подтверждают наблюдаемые инженерные инварианты. +- Установить единый итоговый подход: TypeScript, Biome/ESLint, Vitest/Jest, + Knip, dependency-cruiser, build и native OpenSpec validation — как основные + автоматические доказательства; contract и semantic review остаются + соразмерными риску change. +- Исключить самописные verification и meta-verification механизмы, когда они + лишь оркестрируют, повторяют или тестируют уже применяемые инструменты. +- Не ослаблять проверку типов, стиля, риск-ориентированных тестов, + архитектурных границ, сборки, публичного API contract или проверяемых + repository hygiene-инвариантов. +- Не менять application behavior, API или данные и не вводить CI, новые + зависимости либо новые quality thresholds. + +## Capabilities + +### New Capabilities + +### Modified Capabilities + +## Impact + +Затронуты локальные verification workflows frontend, backend, specs и docs, +OpenSpec agent entrypoints и as-built process documentation. Разработчики и +агенты получат более короткий, прозрачный набор команд; существующие базовые +quality и contract checks сохранятся в итоговой стратегии. diff --git a/openspec/changes/simplify-verification-infrastructure/tasks.md b/openspec/changes/simplify-verification-infrastructure/tasks.md new file mode 100644 index 0000000..76b526d --- /dev/null +++ b/openspec/changes/simplify-verification-infrastructure/tasks.md @@ -0,0 +1,23 @@ +## 1. Заменить избыточное владение verification + +- [x] 1.1 [specs] Удалить project-owned OpenSpec scripts для planning, authority, version и workspace verification вместе с fixtures; обновить schema, store routing и agent entrypoints для native OpenSpec validation и явного завершения по областям. + - **Covers:** `N/A` — Proposal не содержит поведения на уровне Specs; задача реализует утверждённое в Design решение о владении tooling. + - **Verification:** Выполнить native strict validation change и main specs из canonical store. +- [x] 1.2 [backend] Заменить shell verifier runtime OpenAPI на focused Jest integration contract test с явным entrypoint; упростить backend verification pipeline и удалить runtime/hygiene wrappers. + - **Covers:** `N/A` — Публичный API не меняется; задача переносит существующую verification boundary по утверждённому Design. + - **Verification:** Выполнить focused Jest contract test и один раз `npm run verify` в `backend`. +- [x] 1.3 [frontend] Упростить frontend verification pipeline, сохранив OpenAPI generated-type, TypeScript, Biome, Vitest, Knip, dependency-cruiser, build и initial-payload checks, но удалив избыточные wrappers, meta-tests и exact lazy-route baseline behavior. + - **Covers:** `N/A` — Наблюдаемое frontend behavior не меняется; это утверждённое упрощение local gate. + - **Verification:** Выполнить один раз `npm run verify` в `frontend`. + +## 2. Согласовать поставленный workflow и completion evidence + +- [x] 2.1 [docs] Обновить as-built verification strategy, repository workflow documentation и local entrypoints для прямых native и repository gates; сохранить historical evidence как исторический факт. + - **Covers:** `N/A` — Documentation отражает поставленный tooling workflow и не определяет product behavior. + - **Verification:** Выполнить один раз `npm run verify` в `docs` и проверить scoped local links. +- [x] 2.2 [specs] Обновить canonical SDD guidance и change lifecycle references так, чтобы archive требовал native validation, approved artifacts, semantic verification и independent review без удалённого dispatcher. + - **Covers:** `N/A` — Задача согласует versioned process authority с утверждённым Design; Delta Requirement отсутствует. + - **Verification:** Выполнить native strict change validation и strict main-spec validation из `specs`. +- [ ] 2.3 [workspace] До archive по одному разу выполнить gates affected repositories, новый focused OpenAPI contract test, semantic verification against approved artifacts и independent read-only review. + - **Covers:** `N/A` — Workspace владеет только cross-repository completion orchestration, заданной утверждённым Design. + - **Verification:** Зафиксировать один full gate на affected repository, contract test, semantic verification и review outcome без `verify-workspace-change.sh`. diff --git a/openspec/schemas/wealth-ledger-sdd/schema.yaml b/openspec/schemas/wealth-ledger-sdd/schema.yaml index 612cf51..9ef7dcb 100644 --- a/openspec/schemas/wealth-ledger-sdd/schema.yaml +++ b/openspec/schemas/wealth-ledger-sdd/schema.yaml @@ -17,10 +17,8 @@ artifacts: оставь capabilities пустыми и установи `skip_specs: true`. - Оставь технологии, зависимости, архитектуру, API, database/code/file structure для Design, а implementation steps и verification — для Tasks. - - После записи запусти - `bash specs/scripts/verify-openspec-planning.sh `. Исправляй - artifact только при `artifact-validation`; при `invocation` исправь вызов, - при `environment` — prerequisite. Structural success не является approval. + - После записи используй native OpenSpec instructions и validation с + `--store wealth-ledger-specs`. Structural success не является approval. Required Shape: - **Why**: проблема или возможность и причина заняться ею сейчас. @@ -57,9 +55,8 @@ artifacts: `## RENAMED Requirements`; не оставляй пустые operation sections. - Каждый `### Requirement:` содержит SHALL/MUST и хотя бы один `#### Scenario:` с `- **WHEN**` и `- **THEN**`. - - После explicit Proposal approval и записи запусти planning verifier. - Исправляй artifact только при `artifact-validation`; `invocation` и - `environment` не разрешают менять Delta Specs. + - После explicit Proposal approval и записи используй native OpenSpec + validation с `--store wealth-ledger-specs`. Delta Operations: - `ADDED`: новый Requirement. @@ -99,9 +96,8 @@ artifacts: будущие results, commits или Completion Evidence. - Разреши до сохранения любой вопрос, который меняет Specs, approach или Tasks. `Open Questions` оставляет только genuinely deferrable unknowns. - - После explicit Delta Specs approval и записи запусти planning verifier. - Исправляй Design только при `artifact-validation`; `invocation` и - `environment` требуют исправить вызов или prerequisite. + - После explicit Delta Specs approval и записи используй native OpenSpec + validation с `--store wealth-ledger-specs`. Required Shape: - **Context**: current state и constraints подхода. @@ -162,9 +158,8 @@ artifacts: - Новое behavior возвращает change в Specs; новое architecture — в Design; multi-owner или vague task исправляется в Tasks. Не добавляй commits, archive path или post-archive Completion Evidence. - - После записи запусти planning verifier. Исправляй Tasks только при - `artifact-validation`; `invocation` и `environment` не разрешают менять - checklist. + - После записи используй native OpenSpec validation с + `--store wealth-ledger-specs`. Testing: Автоматические tests обязательны для security/privacy, денежных расчётов, @@ -187,8 +182,8 @@ apply: instruction: | Critical Rules: - Apply допустим только после explicit approvals Proposal, Delta Specs, - Design и Tasks. Проверь exact OpenSpec version, canonical store - `wealth-ledger-specs` и planning verifier до implementation. + Design и Tasks. Используй canonical store `wealth-ledger-specs` для всех + OpenSpec commands, читающих или изменяющих change. - Прочитай каждый path из `contextFiles`, возвращённых native OpenSpec apply instructions, затем applicable repository-local `AGENTS.md` affected areas. Не вычисляй planning paths и не создавай второй context map. @@ -202,8 +197,5 @@ apply: нормативного смысла или table alignment. Requirement wording, Scenario, architecture и planned Verification являются semantic changes и требуют соответствующего reapproval. - - `artifact-validation` разрешает исправить указанную planning form; - `invocation` требует исправить вызов; `environment` — prerequisite. - Последние два класса не разрешают менять planning artifact. - Structural validation не заменяет semantic verification и independent read-only review. diff --git a/scripts/fixtures/operation-neutral-change/proposal.md b/scripts/fixtures/operation-neutral-change/proposal.md deleted file mode 100644 index 76e607a..0000000 --- a/scripts/fixtures/operation-neutral-change/proposal.md +++ /dev/null @@ -1,19 +0,0 @@ -## Why - -Controlled fixture validates operation-neutral Delta Spec behavior. - -## What Changes - -- Exercise every supported delta operation in one controlled artifact. - -## Capabilities - -### New Capabilities - -### Modified Capabilities - -- `workspace-artifact-hygiene`: controlled operation grammar fixture. - -## Impact - -Test fixture only; application behavior is not delivered. diff --git a/scripts/fixtures/operation-neutral-change/specs/workspace-artifact-hygiene/spec.md b/scripts/fixtures/operation-neutral-change/specs/workspace-artifact-hygiene/spec.md deleted file mode 100644 index 977585f..0000000 --- a/scripts/fixtures/operation-neutral-change/specs/workspace-artifact-hygiene/spec.md +++ /dev/null @@ -1,38 +0,0 @@ -## ADDED Requirements - -### Requirement: Controlled fixture requirement - -The fixture SHALL provide one complete added requirement. - -#### Scenario: Added operation is complete - -- **WHEN** the controlled delta is validated -- **THEN** the added requirement has observable behavior - -## MODIFIED Requirements - -### Requirement: Рабочая frontend configuration не является versioned input - -Frontend SHALL поставлять versioned `.env.example`, достаточный для создания -локального `.env`, но MUST NOT хранить рабочий `.env` или другой локальный -`.env*` файл в source control. Ignore policy MUST исключать локальные frontend -configuration files, сохраняя явно разрешённый example template. - -#### Scenario: Участник создаёт local frontend configuration - -- **WHEN** участник копирует `.env.example` в `.env` для запуска frontend -- **THEN** локальный файл доступен runtime, но не появляется среди tracked - файлов Git - -## REMOVED Requirements - -### Requirement: Runtime SQLite-данные не являются versioned artifact - -**Reason**: Controlled fixture exercises the removal grammar. - -**Migration**: Existing behavior remains authoritative outside this fixture. - -## RENAMED Requirements - -- FROM: `### Requirement: Dependency advisory получает проверяемое решение` -- TO: `### Requirement: Dependency advisory получает контролируемое решение` diff --git a/scripts/lib/delivery-map.sh b/scripts/lib/delivery-map.sh deleted file mode 100644 index a1eba5c..0000000 --- a/scripts/lib/delivery-map.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env bash - -wl_area_index() { - case "$1" in - frontend) printf '0' ;; - backend) printf '1' ;; - specs) printf '2' ;; - docs) printf '3' ;; - *) return 1 ;; - esac -} - -wl_trim() { - local value=$1 - value=${value#"${value%%[![:space:]]*}"} - value=${value%"${value##*[![:space:]]}"} - printf '%s' "$value" -} - -wl_parse_delivery_map() { - local metadata_file=$1 - local design_file=$2 - local raw_area raw_status raw_reason area status reason gate index - - wl_areas=(frontend backend specs docs) - wl_affected_flags=(0 0 0 0) - wl_map_statuses=('' '' '' '') - wl_map_reasons=('' '' '' '') - wl_contract_status='' - wl_contract_reason='' - - while IFS= read -r area; do - [[ -n "$area" ]] || continue - if ! index=$(wl_area_index "$area"); then - printf 'Unknown affected area in metadata: %s\n' "$area" >&2 - return 1 - fi - if [[ "${wl_affected_flags[$index]}" -eq 1 ]]; then - printf 'Duplicate affected area in metadata: %s\n' "$area" >&2 - return 1 - fi - wl_affected_flags[$index]=1 - done < <( - awk ' - $0 == "affected_areas:" { in_areas = 1; next } - in_areas && /^ - / { sub(/^ - /, ""); print; next } - in_areas { exit } - ' "$metadata_file" - ) - - if [[ "${wl_affected_flags[2]}" -ne 1 ]]; then - printf 'Delivery Map violation: specs must be affected for every versioned change.\n' >&2 - return 1 - fi - - while IFS='|' read -r _ raw_area raw_status raw_reason _; do - area=$(wl_trim "$raw_area") - case "$area" in - frontend|backend|specs|docs) ;; - *) continue ;; - esac - index=$(wl_area_index "$area") - if [[ -n "${wl_map_statuses[$index]}" ]]; then - printf 'Delivery Map contains duplicate row for %s.\n' "$area" >&2 - return 1 - fi - status=$(wl_trim "$raw_status") - status=${status//\`/} - reason=$(wl_trim "$raw_reason") - wl_map_statuses[$index]=$status - wl_map_reasons[$index]=$reason - done < "$design_file" - - while IFS='|' read -r _ raw_area raw_status raw_reason _; do - gate=$(wl_trim "$raw_area") - [[ "$gate" == 'runtime-openapi' ]] || continue - if [[ -n "$wl_contract_status" ]]; then - printf 'Contract Gate Decision contains duplicate runtime-openapi row.\n' >&2 - return 1 - fi - wl_contract_status=$(wl_trim "$raw_status") - wl_contract_status=${wl_contract_status//\`/} - wl_contract_reason=$(wl_trim "$raw_reason") - done < "$design_file" - - for index in 0 1 2 3; do - area=${wl_areas[$index]} - status=${wl_map_statuses[$index]} - reason=${wl_map_reasons[$index]} - if [[ -z "$status" ]]; then - printf 'Delivery Map is missing area: %s\n' "$area" >&2 - return 1 - fi - if [[ "${wl_affected_flags[$index]}" -eq 1 ]]; then - if [[ "$status" != 'affected' ]]; then - printf 'Delivery Map mismatch: %s is listed as affected in metadata but has status %s.\n' "$area" "$status" >&2 - return 1 - fi - elif [[ "$status" != 'N/A' ]]; then - printf 'Delivery Map mismatch: %s is absent from affected_areas but has status %s.\n' "$area" "$status" >&2 - return 1 - fi - if [[ -z "$reason" || "$reason" == '-' || "$reason" == '—' || "$reason" == *''* ]] || fail "task $task_number has unresolved N/A rationale placeholder" - covers_is_na=1 - else - IFS=';' read -r -a references <<< "$payload" - for reference in "${references[@]}"; do - reference=$(wl_trim "$reference") - if ! grep -Eq '^`[^`]+` — `[^`]+`$' <<< "$reference"; then - fail "task $task_number has malformed requirement reference: $reference" - fi - clean=${reference//\`/} - capability=${clean%% — *} - requirement=${clean#* — } - key="$capability"$'\t'"$requirement" - if ! grep -Fqx "$key" "$delta_requirements"; then - fail "task $task_number has unknown requirement reference: $capability — $requirement" - fi - printf '%s\n' "$key" >> "$covered_requirements" - done - fi - continue - fi - - if [[ "$line" == ' - **Verification:** '* ]]; then - [[ -n "$task_number" ]] || fail 'Verification metadata appears before a task' - verification_count=$((verification_count + 1)) - verification=${line#' - **Verification:** '} - [[ -n "$verification" ]] || fail "task $task_number has empty Verification" - if [[ "$task_area" != 'workspace' ]]; then - index=$(wl_area_index "$task_area") - case "$task_area" in - frontend|backend|docs) - [[ "$verification" == *'npm run verify'* ]] && area_gate_counts[$index]=$((area_gate_counts[$index] + 1)) - ;; - specs) - if [[ "$verification" == *'strict change validation'* || "$verification" == *'openspec validate'* ]]; then - area_gate_counts[$index]=$((area_gate_counts[$index] + 1)) - fi - ;; - esac - fi - fi - done < "$tasks_file" - finalize_task - [[ "$task_count" -gt 0 ]] || fail 'Tasks contains no tracked checkboxes' - - for index in 0 1 2 3; do - if [[ "${wl_affected_flags[$index]}" -eq 1 ]]; then - [[ "${area_task_counts[$index]}" -gt 0 ]] || fail "affected area ${wl_areas[$index]} has no owned task" - [[ "${area_gate_counts[$index]}" -gt 0 ]] || fail "affected area ${wl_areas[$index]} has no canonical gate in Verification" - fi - done - - while IFS= read -r key; do - [[ -n "$key" ]] || continue - if ! grep -Fqx "$key" "$covered_requirements"; then - capability=${key%%$'\t'*} - requirement=${key#*$'\t'} - fail "uncovered Requirement: $capability — $requirement" - fi - done < "$delta_requirements" - - requirement_count=$(wc -l < "$delta_requirements" | tr -d ' ') - printf 'Delta requirement coverage verified: %s/%s\n' "$requirement_count" "$requirement_count" -fi - -if [[ $has_tasks -eq 1 ]]; then - printf 'Planning stage verified: tasks\n' - printf 'Full planning structure verified; semantic approval remains human.\n' -elif [[ $has_design -eq 1 ]]; then - printf 'Planning stage verified: design\n' - printf 'Full change validation pending: tasks\n' -elif [[ $has_specs -eq 1 ]]; then - printf 'Planning stage verified: specs\n' - printf 'Full change validation pending: design, tasks\n' -else - printf 'Planning stage verified: proposal\n' - printf 'Full change validation pending: specs, design, tasks\n' -fi diff --git a/scripts/verify-openspec-planning.test.sh b/scripts/verify-openspec-planning.test.sh deleted file mode 100755 index 1e0113a..0000000 --- a/scripts/verify-openspec-planning.test.sh +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) -workspace=$(cd "$script_dir/../.." && pwd -P) -verifier="$script_dir/verify-openspec-planning.sh" -changes_root="$workspace/specs/openspec/changes" -source_change="$changes_root/harden-openspec-sdd-quality" -if [[ ! -d "$source_change" ]]; then - source_change="$changes_root/archive/2026-08-26-harden-openspec-sdd-quality" -fi -prefix="planning-fixture-$$" -created_changes=('') - -cleanup() { - local change - for change in "${created_changes[@]}"; do - [[ -n "$change" ]] || continue - rm -rf "$changes_root/$change" - done -} -trap cleanup EXIT - -if [[ ! -x "$verifier" ]]; then - printf 'expected executable planning verifier at %s\n' "$verifier" >&2 - exit 1 -fi - -make_change() { - local suffix=$1 - fixture_change="$prefix-$suffix" - cp -R "$source_change" "$changes_root/$fixture_change" - created_changes+=("$fixture_change") -} - -run_verifier() { - bash "$verifier" "$1" -} - -expect_failure() { - local change=$1 - local expected=$2 - local expected_status=${3:-1} - local output - local status - - set +e - output=$(run_verifier "$change" 2>&1) - status=$? - set -e - if [[ $status -eq 0 ]]; then - printf 'expected planning verification to fail for %s\n' "$change" >&2 - exit 1 - fi - if [[ $status -ne $expected_status ]]; then - printf 'expected exit %s for %s, got %s:\n%s\n' "$expected_status" "$change" "$status" "$output" >&2 - exit 1 - fi - if [[ "$output" != *'Planning artifact-validation failed:'* ]]; then - printf 'expected artifact-validation category for %s, got:\n%s\n' "$change" "$output" >&2 - exit 1 - fi - if [[ "$output" != *"$expected"* ]]; then - printf 'expected failure containing %q, got:\n%s\n' "$expected" "$output" >&2 - exit 1 - fi -} - -make_change proposal -proposal_change=$fixture_change -rm -rf "$changes_root/$proposal_change/specs" -rm -f "$changes_root/$proposal_change/design.md" "$changes_root/$proposal_change/tasks.md" -proposal_output=$(run_verifier "$proposal_change") -[[ "$proposal_output" == *'Planning stage verified: proposal'* ]] -[[ "$proposal_output" == *'Full change validation pending: specs, design, tasks'* ]] - -set +e -invocation_output=$(bash "$verifier" 'INVALID CHANGE' 2>&1) -invocation_status=$? -set -e -if [[ $invocation_status -ne 2 || "$invocation_output" != *'Planning invocation failed:'* ]]; then - printf 'expected invalid-name invocation failure, got exit %s:\n%s\n' "$invocation_status" "$invocation_output" >&2 - exit 1 -fi - -set +e -missing_change_output=$(bash "$verifier" "$prefix-missing" 2>&1) -missing_change_status=$? -set -e -if [[ $missing_change_status -ne 2 || "$missing_change_output" != *'Planning invocation failed:'* ]]; then - printf 'expected missing-change invocation failure, got exit %s:\n%s\n' "$missing_change_status" "$missing_change_output" >&2 - exit 1 -fi - -set +e -environment_output=$(PATH=/usr/bin:/bin bash "$verifier" "$proposal_change" 2>&1) -environment_status=$? -set -e -if [[ $environment_status -ne 3 || "$environment_output" != *'Planning environment failed:'* ]]; then - printf 'expected environment failure, got exit %s:\n%s\n' "$environment_status" "$environment_output" >&2 - exit 1 -fi - -make_change skip-conflict -skip_conflict=$fixture_change -rm -rf "$changes_root/$skip_conflict/specs" -rm -f "$changes_root/$skip_conflict/design.md" "$changes_root/$skip_conflict/tasks.md" -printf 'skip_specs: true\n' >> "$changes_root/$skip_conflict/.openspec.yaml" -expect_failure "$skip_conflict" 'skip_specs: true cannot be combined with declared capabilities' - -make_change specs -specs_change=$fixture_change -rm -f "$changes_root/$specs_change/design.md" "$changes_root/$specs_change/tasks.md" -specs_output=$(run_verifier "$specs_change") -[[ "$specs_output" == *'Planning stage verified: specs'* ]] -[[ "$specs_output" == *'Full change validation pending: design, tasks'* ]] - -make_change capability-mismatch -capability_mismatch=$fixture_change -mv "$changes_root/$capability_mismatch/specs/audit-remediation-workflow" "$changes_root/$capability_mismatch/specs/not-declared" -expect_failure "$capability_mismatch" 'delta capability is absent from Proposal: not-declared' - -make_change design -design_change=$fixture_change -rm -f "$changes_root/$design_change/tasks.md" -design_output=$(run_verifier "$design_change") -[[ "$design_output" == *'Planning stage verified: design'* ]] -[[ "$design_output" == *'Full change validation pending: tasks'* ]] - -make_change required-rollback -required_rollback=$fixture_change -rm -f "$changes_root/$required_rollback/tasks.md" -perl -0pi -e 's/\| Status \| `N\/A` \|/| Status | `required` |/; s/(\| Verification \|)/| Trigger | Deployment fails the local gate. |\n| Actions | Restore the previous local state. |\n| Data safety | No application data changes. |\n$1/' "$changes_root/$required_rollback/design.md" -required_output=$(run_verifier "$required_rollback") -[[ "$required_output" == *'Planning stage verified: design'* ]] - -perl -0pi -e 's/^\| Data safety \|.*\n//m' "$changes_root/$required_rollback/design.md" -expect_failure "$required_rollback" 'Rollback Contract required status needs exactly one Data safety row' - -make_change tasks -tasks_change=$fixture_change -tasks_output=$(run_verifier "$tasks_change") -[[ "$tasks_output" == *'Planning stage verified: tasks'* ]] -[[ "$tasks_output" == *'Delta requirement coverage verified: 9/9'* ]] -[[ "$tasks_output" == *'Full planning structure verified; semantic approval remains human.'* ]] - -for artifact in proposal specs design tasks; do - instructions=$(cd "$workspace/specs" && openspec instructions "$artifact" --change "$tasks_change" --store wealth-ledger-specs --json) - node -e ' - const result = JSON.parse(require("node:fs").readFileSync(0, "utf8")); - if (!result.instruction.trimStart().startsWith("Critical Rules:")) process.exit(1); - ' <<< "$instructions" -done - -specs_instructions=$(cd "$workspace/specs" && openspec instructions specs --change "$tasks_change" --store wealth-ledger-specs --json) -node -e ' - const result = JSON.parse(require("node:fs").readFileSync(0, "utf8")); - const activeOperation = /^## (ADDED|MODIFIED|REMOVED|RENAMED) Requirements$/m; - if (activeOperation.test(result.template)) process.exit(1); - for (const token of ["Purpose", "ADDED", "MODIFIED", "REMOVED", "RENAMED"]) { - if (!result.template.includes(token)) process.exit(1); - } -' <<< "$specs_instructions" - -operation_change="$prefix-operation-neutral" -cp -R "$script_dir/fixtures/operation-neutral-change" "$changes_root/$operation_change" -created_changes+=("$operation_change") -operation_output=$(run_verifier "$operation_change") -if [[ "$operation_output" != *'Planning stage verified: specs'* ]]; then - printf 'expected controlled operation fixture to verify, got:\n%s\n' "$operation_output" >&2 - exit 1 -fi -for operation in ADDED MODIFIED REMOVED RENAMED; do - if [[ $(grep -Fxc "## $operation Requirements" "$changes_root/$operation_change/specs/workspace-artifact-hygiene/spec.md") -ne 1 ]]; then - printf 'expected exactly one controlled %s operation section\n' "$operation" >&2 - exit 1 - fi -done - -# A completed fixture receives OpenSpec's terminal archive message instead of -# the schema's Apply instruction, so leave one task pending for this contract. -perl -0pi -e 's/^- \[[xX]\]/- [ ]/m' "$changes_root/$tasks_change/tasks.md" -apply_instructions=$(cd "$workspace/specs" && openspec instructions apply --change "$tasks_change" --store wealth-ledger-specs --json) -node -e ' - const result = JSON.parse(require("node:fs").readFileSync(0, "utf8")); - for (const token of ["contextFiles", "AGENTS.md", "planned Verification", "Feedback routing", "Механическая правка", "Requirement wording"]) { - if (!result.instruction.includes(token)) process.exit(1); - } -' <<< "$apply_instructions" - -design_instructions=$(cd "$workspace/specs" && openspec instructions design --change "$tasks_change" --store wealth-ledger-specs --json) -node -e ' - const result = JSON.parse(require("node:fs").readFileSync(0, "utf8")); - if (!result.instruction.includes("инвалидируй downstream approvals")) process.exit(1); -' <<< "$design_instructions" - -make_change invalid-area -invalid_area=$fixture_change -perl -0pi -e 's/\[specs\]/[frontend]/' "$changes_root/$invalid_area/tasks.md" -expect_failure "$invalid_area" 'task 1.1 uses frontend, but Delivery Map marks it N/A' - -make_change unknown-reference -unknown_reference=$fixture_change -perl -0pi -e 's/`Planning artifact проверяется до запроса approval`/`Несуществующее требование`/' "$changes_root/$unknown_reference/tasks.md" -expect_failure "$unknown_reference" 'unknown requirement reference' - -make_change uncovered-requirement -uncovered_requirement=$fixture_change -sed -i '' '/`audit-remediation-workflow` — `Каждое audit claim получает доказательный disposition`/s/`audit-remediation-workflow` — `Каждое audit claim получает доказательный disposition`/`audit-remediation-workflow` — `Реестр remediation отделён от снимка аудита`/' "$changes_root/$uncovered_requirement/tasks.md" -expect_failure "$uncovered_requirement" 'uncovered Requirement: audit-remediation-workflow — Каждое audit claim получает доказательный disposition' - -make_change missing-metadata -missing_metadata=$fixture_change -perl -0pi -e 's/^ - \*\*Verification:\*\*.*\n//m' "$changes_root/$missing_metadata/tasks.md" -expect_failure "$missing_metadata" 'task 1.1 must contain exactly one Verification line' - -make_change unresolved-na-placeholder -unresolved_na_placeholder=$fixture_change -perl -0pi -e 's/^ - \*\*Covers:\*\*.*$/ - **Covers:** `N\/A` — /m' "$changes_root/$unresolved_na_placeholder/tasks.md" -expect_failure "$unresolved_na_placeholder" 'task 1.1 has unresolved N/A rationale placeholder' - -make_change empty-na -empty_na=$fixture_change -perl -0pi -e 's/^ - \*\*Covers:\*\*.*$/ - **Covers:** `N\/A` — /m' "$changes_root/$empty_na/tasks.md" -expect_failure "$empty_na" 'task 1.1 has empty N/A rationale' - -make_change separator-na -separator_na=$fixture_change -perl -0pi -e 's/^ - \*\*Covers:\*\*.*$/ - **Covers:** `N\/A` — —/m' "$changes_root/$separator_na/tasks.md" -expect_failure "$separator_na" 'task 1.1 has empty N/A rationale' - -make_change malformed-rollback -malformed_rollback=$fixture_change -sed -i '' '/| Status | `N\/A` |/d' "$changes_root/$malformed_rollback/design.md" -expect_failure "$malformed_rollback" 'Rollback Contract must contain exactly one Status row' - -printf 'verify-openspec-planning tests passed\n' diff --git a/scripts/verify-openspec-version.sh b/scripts/verify-openspec-version.sh deleted file mode 100755 index 11041ce..0000000 --- a/scripts/verify-openspec-version.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) -contract_file=${1:-"$script_dir/../openspec/.cli-version"} - -if [[ ! -f "$contract_file" ]]; then - printf 'OpenSpec version contract is missing: %s\n' "$contract_file" >&2 - exit 1 -fi - -expected_version=$(tr -d '[:space:]' < "$contract_file") -if [[ -z "$expected_version" ]]; then - printf 'OpenSpec version contract is empty: %s\n' "$contract_file" >&2 - exit 1 -fi - -if ! command -v openspec >/dev/null 2>&1; then - printf 'OpenSpec CLI %s is required, but openspec is not available in PATH.\n' "$expected_version" >&2 - exit 1 -fi - -actual_version=$(openspec --version 2>/dev/null | tr -d '\r\n') -if [[ "$actual_version" != "$expected_version" ]]; then - printf 'Unsupported OpenSpec CLI version: expected %s, actual %s.\n' "$expected_version" "${actual_version:-unknown}" >&2 - exit 1 -fi - -printf 'OpenSpec CLI version verified: %s\n' "$actual_version" diff --git a/scripts/verify-runtime-openapi.sh b/scripts/verify-runtime-openapi.sh deleted file mode 100755 index 2e086ce..0000000 --- a/scripts/verify-runtime-openapi.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -workspace_dir="$(cd -- "$script_dir/../.." && pwd)" -backend_dir="$workspace_dir/backend" -snapshot_path="$workspace_dir/frontend/src/shared/api/spec/openapi.json" - -if [[ ! -f "$snapshot_path" ]]; then - echo "Missing committed OpenAPI snapshot: $snapshot_path" >&2 - exit 1 -fi - -node_major="$(node -p "process.versions.node.split('.')[0]")" -if [[ "$node_major" != "22" ]]; then - echo "Node 22 is required; found $(node --version)." >&2 - exit 1 -fi - -temporary_dir="$(mktemp -d "${TMPDIR:-/tmp}/wealth-ledger-runtime-openapi.XXXXXX")" -backend_pid="" - -cleanup() { - if [[ -n "$backend_pid" ]] && kill -0 "$backend_pid" 2>/dev/null; then - kill "$backend_pid" 2>/dev/null || true - wait "$backend_pid" 2>/dev/null || true - fi - rm -rf "$temporary_dir" -} -trap cleanup EXIT INT TERM - -port="$(node -e "const server = require('node:net').createServer(); server.listen(0, '127.0.0.1', () => { console.log(server.address().port); server.close(); });")" -runtime_path="$temporary_dir/runtime.json" -canonical_runtime_path="$temporary_dir/runtime.canonical.json" -canonical_snapshot_path="$temporary_dir/snapshot.canonical.json" -backend_log="$temporary_dir/backend.log" -compile_log="$temporary_dir/compile.log" - -if ! ( - cd "$backend_dir" - ./node_modules/.bin/tsc --project tsconfig.build.json --outDir "$temporary_dir/dist" -) >"$compile_log" 2>&1; then - echo "Temporary backend compilation failed:" >&2 - sed -n '1,160p' "$compile_log" >&2 - exit 1 -fi -ln -s "$backend_dir/node_modules" "$temporary_dir/dist/node_modules" - -( - cd "$backend_dir" - NODE_ENV=development \ - PORT="$port" \ - DATABASE_URL="file:$temporary_dir/runtime.db" \ - FRONTEND_ORIGIN="http://localhost:5173" \ - JWT_SECRET="runtime-openapi-check-secret-at-least-32" \ - TOKEN_ENCRYPTION_KEY="MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=" \ - TBANK_GRPC_ENDPOINT="127.0.0.1:1" \ - TBANK_RATE_LIMIT_PER_SECOND=5 \ - LOG_LEVEL=error \ - node "$temporary_dir/dist/src/main.js" -) >"$backend_log" 2>&1 & -backend_pid="$!" - -for _ in $(seq 1 60); do - if node -e "fetch(process.argv[1]).then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))" "http://127.0.0.1:$port/api/docs-json"; then - break - fi - if ! kill -0 "$backend_pid" 2>/dev/null; then - echo "Backend stopped before exposing /api/docs-json:" >&2 - sed -n '1,160p' "$backend_log" >&2 - exit 1 - fi - sleep 0.25 -done - -if ! node -e 'fetch(process.argv[1]).then(async (response) => { if (!response.ok) throw new Error(`${response.status} ${response.statusText}`); require("node:fs").writeFileSync(process.argv[2], await response.text()); }).catch((error) => { console.error(`OpenAPI fetch failed: ${error.message}`); process.exit(1); })' "http://127.0.0.1:$port/api/docs-json" "$runtime_path"; then - echo "Backend did not expose /api/docs-json within 15 seconds:" >&2 - sed -n '1,160p' "$backend_log" >&2 - exit 1 -fi - -if node - "$runtime_path" "$snapshot_path" "$canonical_runtime_path" "$canonical_snapshot_path" <<'NODE' -const { createHash } = require('node:crypto'); -const { readFileSync, writeFileSync } = require('node:fs'); - -function canonicalize(value) { - if (Array.isArray(value)) return value.map(canonicalize); - if (value && typeof value === 'object') { - return Object.fromEntries( - Object.keys(value) - .sort() - .map((key) => [key, canonicalize(value[key])]), - ); - } - return value; -} - -const [runtimePath, snapshotPath, canonicalRuntimePath, canonicalSnapshotPath] = - process.argv.slice(2); -const documents = [runtimePath, snapshotPath].map((path) => - canonicalize(JSON.parse(readFileSync(path, 'utf8'))), -); -const serialized = documents.map((document) => `${JSON.stringify(document, null, 2)}\n`); -writeFileSync(canonicalRuntimePath, serialized[0]); -writeFileSync(canonicalSnapshotPath, serialized[1]); -const hashes = serialized.map((value) => - createHash('sha256').update(value).digest('hex'), -); -console.log(`runtime SHA-256: ${hashes[0]}`); -console.log(`snapshot SHA-256: ${hashes[1]}`); -process.exitCode = hashes[0] === hashes[1] ? 0 : 1; -NODE -then - comparison_status=0 -else - comparison_status=$? -fi - -if [[ "$comparison_status" -ne 0 ]]; then - echo "Runtime OpenAPI differs from the committed frontend snapshot:" >&2 - diff -u "$canonical_snapshot_path" "$canonical_runtime_path" || true - exit 1 -fi - -echo "Runtime OpenAPI matches the committed frontend snapshot." diff --git a/scripts/verify-workspace-change.sh b/scripts/verify-workspace-change.sh deleted file mode 100755 index ccf6c9d..0000000 --- a/scripts/verify-workspace-change.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [[ $# -ne 1 ]]; then - printf 'Usage: %s \n' "${0##*/}" >&2 - exit 2 -fi - -change=$1 -if [[ ! "$change" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then - printf 'Invalid OpenSpec change name: %s\n' "$change" >&2 - exit 2 -fi - -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) -workspace=$(cd "$script_dir/../.." && pwd -P) -change_dir="$workspace/specs/openspec/changes/$change" -metadata_file="$change_dir/.openspec.yaml" -design_file="$change_dir/design.md" - -for required_file in "$metadata_file" "$design_file"; do - if [[ ! -f "$required_file" ]]; then - printf 'Missing canonical change input: %s\n' "${required_file#$workspace/}" >&2 - exit 1 - fi -done - -# shellcheck source=lib/delivery-map.sh -source "$script_dir/lib/delivery-map.sh" -wl_parse_delivery_map "$metadata_file" "$design_file" - -areas=("${wl_areas[@]}") -affected_flags=("${wl_affected_flags[@]}") -map_statuses=("${wl_map_statuses[@]}") -contract_status=$wl_contract_status - -printf 'Delivery Map verified:' -for index in 0 1 2 3; do - printf ' %s=%s' "${areas[$index]}" "${map_statuses[$index]}" -done -printf '\n' -printf 'Contract Gate Decision verified: runtime-openapi=%s\n' "$contract_status" - -run_gate() { - local label=$1 - shift - printf '\n==> %s\n' "$label" - "$@" -} - -run_gate 'OpenSpec CLI contract' bash "$workspace/specs/scripts/verify-openspec-version.sh" -run_gate 'OpenSpec planning structure' bash "$workspace/specs/scripts/verify-openspec-planning.sh" "$change" -run_gate 'OpenSpec project schema' bash -c 'cd "$1" && openspec schema validate wealth-ledger-sdd' _ "$workspace/specs" -run_gate 'OpenSpec change validation' bash -c 'cd "$1" && openspec validate "$2" --type change --strict --store wealth-ledger-specs' _ "$workspace/specs" "$change" -run_gate 'OpenSpec main specs' bash -c 'cd "$1" && openspec validate --specs --strict --store wealth-ledger-specs' _ "$workspace/specs" -run_gate 'OpenSpec authority' bash "$workspace/specs/scripts/verify-openspec-authority.sh" - -if [[ "${affected_flags[0]}" -eq 1 ]]; then - run_gate 'Frontend canonical verification' npm --prefix "$workspace/frontend" run verify -fi - -if [[ "${affected_flags[1]}" -eq 1 ]]; then - run_gate 'Backend canonical verification' npm --prefix "$workspace/backend" run verify -fi - -if [[ "${affected_flags[3]}" -eq 1 ]]; then - run_gate 'Docs canonical verification' npm --prefix "$workspace/docs" run verify -fi - -if [[ "$contract_status" == "required" ]]; then - run_gate 'Runtime OpenAPI contract' bash "$workspace/specs/scripts/verify-runtime-openapi.sh" -fi - -printf '\nWorkspace verification passed for %s.\n' "$change"