From 72444dbbb11ae267e349adbf35256bd7c5be1489 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 23 Aug 2026 21:21:22 +0300 Subject: [PATCH] docs(specs): archive local verification remediation --- AGENTS.md | 1 + .../.openspec.yaml | 3 + .../README.md | 5 + .../design.md | 103 ++++++++++++++ .../proposal.md | 42 ++++++ .../specs/api-contract-generation/spec.md | 38 ++++++ .../specs/project-foundation/spec.md | 27 ++++ .../tasks.md | 58 ++++++++ .../specs/api-contract-generation/spec.md | 30 ++++- openspec/specs/project-foundation/spec.md | 12 +- scripts/verify-runtime-openapi.sh | 126 ++++++++++++++++++ 11 files changed, 441 insertions(+), 4 deletions(-) create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/.openspec.yaml create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/README.md create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/design.md create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/proposal.md create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/api-contract-generation/spec.md create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/project-foundation/spec.md create mode 100644 openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/tasks.md create mode 100755 scripts/verify-runtime-openapi.sh diff --git a/AGENTS.md b/AGENTS.md index da71a38..38d099f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,7 @@ openspec validate --type change --store wealth-ledger-specs openspec instructions apply --change --json --store wealth-ledger-specs bash scripts/verify-openspec-authority.sh +bash scripts/verify-runtime-openapi.sh ``` ## Границы diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/.openspec.yaml b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/.openspec.yaml new file mode 100644 index 0000000..1957c84 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-08-23 +goal: Закрыть WL-P1-05 и WL-P2-06, WL-P2-09, WL-P2-11; явно отложить WL-P2-05 и WL-P2-13 до стабильного Prisma migration gate. diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/README.md b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/README.md new file mode 100644 index 0000000..71b2808 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/README.md @@ -0,0 +1,5 @@ +# make-local-verification-agent-safe + +Сделать конкретные локальные backend и runtime OpenAPI проверки +детерминированными и безопасными для агента; не маскировать заблокированный +Prisma e2e migration gate общим verdict. diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/design.md b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/design.md new file mode 100644 index 0000000..a0b76a6 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/design.md @@ -0,0 +1,103 @@ +## Context + +Корень workspace — coordination directory, а не package-manager workspace и не +Git-репозиторий. `frontend`, `backend`, `specs` и `docs` — независимые Git +roots. `specs/scripts/verify-openspec-authority.sh` уже показывает правильное +место для versioned cross-repository tooling: script определяет workspace +относительно себя, а не доверяет текущей директории. + +Frontend закрепляет Node 22 и имеет offline `npm run verify`. Для backend нет +эквивалентных runtime policy и отдельных quality gates: `lint` запускает ESLint +с `--fix`, base tsconfig включает тесты, а build tsconfig исключает их. +Существующие e2e tests создают in-memory Nest application, но их script сначала +запускает Prisma на фиксированной `/tmp` DB и сейчас падает до Jest. Swagger +доступен по `/api/docs-json` при `NODE_ENV=development`; +`frontend/scripts/openapi:sync.mjs` — единственный намеренный writer committed +snapshot и generated types. + +## Goals / Non-Goals + +**Цели:** + +- Восстановить исполнимые backend gates для lint и TypeScript приложения и + тестов с понятной диагностикой failures. +- Доказать отдельной read-only командой, что полный runtime OpenAPI document + совпадает с committed frontend snapshot. +- Сделать Node/npm bootstrap и конкретные verification commands discoverable + для агентов. +- Сохранить pre-existing working-tree state пользователя; не создавать + versioned writes во время проверок. + +**Не входит в scope:** + +- CI/CD, remote verification, Docker, новый task runner, package-manager + workspace или общий workspace verifier. +- Изоляция backend e2e DB, обновление Prisma packages и восстановление + migration gate: WL-P2-05 и зависимый WL-P2-13 отложены из-за нестабильности + Prisma 7.9.1 на Node 22. +- JSON report format, cached results, plug-in architecture, arbitrary diff + bases, сохранённые hashes либо fixture/unit-test harness для process scripts. +- Изменение OpenAPI sync writer, generated frontend types, пользовательских API + statuses, T-Bank integration, application features, перенос business glossary + `CONTEXT.md` или удаление существующего OpenSpec authority fixture test. + +## Decisions + +### Backend check и fix — разные операции + +Backend предоставляет `lint:check` без `--fix`, `lint:fix` с явным write intent +и сохраняет `lint` как совместимый alias для `lint:check`. Он предоставляет +отдельные команды `typecheck:app` и `typecheck:test` на основе выделенных +tsconfig files. Эти команды не создают repository artifacts; build и e2e не +объявляются частью нового общего verdict. + +### Runtime OpenAPI сверяется отдельной read-only командой + +`specs/scripts/verify-runtime-openapi.sh` определяет workspace из собственного +пути, поднимает backend с детерминированным development environment, ждёт local +readiness, получает `/api/docs-json` и всегда завершает child process. Команда +рекурсивно упорядочивает JSON object keys, сохраняя array order, записывает +canonical runtime и snapshot documents во временную директорию, сравнивает их +SHA-256 и выводит unified diff при различии. + +Она никогда не вызывает `openapi:sync`, не записывает frontend snapshot и не +обновляет generated types. Это узкий контрактный gate, а не orchestration +interface для всех репозиториев. + +### Bootstrap и документация задают один contract + +Оба приложения требуют Node 22 и `npm ci`. Scoped `AGENTS.md` перечисляют +только реально существующие non-mutating verification commands и явно отделяют +писатели `lint:fix` и `openapi:sync`. Durable development documentation +показывает prerequisites и границы отдельной runtime-contract команды. + +### Prisma findings явно отложены, а не маскируются обходом + +`prisma migrate deploy` 7.9.1 воспроизводимо возвращает пустой schema-engine +error как на фиксированной, так и на уникальной временной SQLite DB; включение +engine diagnostics меняет результат. На 2026-08-23 опубликованная latest +stable version также 7.9.1. Не вводится второй migration mechanism и не +обновляются зависимости без подтверждённого stable candidate. Реестр фиксирует +дату, причину и trigger review: следующая stable Prisma должна трижды применить +existing migrations к чистой DB на Node 22. + +## Risks / Trade-offs + +- [Runtime backend не стартует или child process остаётся после ошибки] → + command создаёт cleanup trap до запуска backend и сохраняет фактический exit + status. +- [Canonicalization скрывает различие контрактов] → нормализуется только + порядок object keys; arrays и каждое JSON field остаются частью SHA-256 input. +- [Prisma schema engine остаётся недоступным на supported runtime] → WL-P2-05 + и WL-P2-13 остаются `deferred` до опубликованной stable версии, прошедшей + оговорённый reproduction gate. +- [Ignored artifacts путаются с versioned writes] → отдельные команды не + считают `build` или e2e своим успехом и документируют свои временные ресурсы. + +## Migration Plan + +Не нужны migration production data или deployment sequence. Разработчики +запускают `npm ci` под Node 22 в каждом application repository, используют +`npm run lint:fix` только осознанно и запускают runtime OpenAPI check отдельно +из workspace. Rollback — обычный revert independently versioned changes; он не +меняет committed API artifacts или persistent user data. diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/proposal.md b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/proposal.md new file mode 100644 index 0000000..e648753 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/proposal.md @@ -0,0 +1,42 @@ +## Why + +В workspace четыре независимых Git-репозитория, но нет единого +воспроизводимого verdict локальной проверки. Backend `lint` изменяет исходные +файлы, backend e2e останавливается на настройке Prisma до Jest, для тестового +TypeScript нет исполнимого gate, а committed frontend OpenAPI snapshot не +сверяется с runtime-документом. Из-за этого агент может получить зелёный либо +неполный результат, не узнав об изменении исходников или дрейфе +межрепозиторного контракта. + +## What Changes + +- Разделить backend lint-проверку и явный auto-fix, добавить отдельные + TypeScript gates для приложения и тестов. +- Добавить сверку runtime OpenAPI с committed snapshot: она поднимает + детерминированный локальный backend, canonicalize оба полных документа во + временном хранилище, сравнивает SHA-256 и показывает diff при дрейфе, не + вызывая `openapi:sync`. +- Унифицировать local bootstrap frontend и backend на Node 22 и `npm ci`, + документировать конкретные gates и обновить scoped agent instructions. +- Обновить audit remediation registry: этот change станет единственным active + remediation для WL-P1-05, WL-P2-06, WL-P2-09 и WL-P2-11; WL-P2-05 и + WL-P2-13 будут явно отложены до стабильного Prisma migration gate. + +## Capabilities + +### Modified Capabilities + +- `project-foundation`: Выравнивает требования к runtime/bootstrap приложений и + локальные agent verification instructions frontend и backend. +- `api-contract-generation`: Требует явной локальной runtime сверки между + backend OpenAPI и committed frontend snapshot. + +## Impact + +Затронуты `backend`, `frontend`, `specs` и `docs`. Изменятся backend package +scripts, TypeScript-конфигурации, local runtime metadata и документация; +generated frontend snapshot и offline-поведение `npm run verify` не изменятся. +Небольшая read-only runtime-contract команда в `specs/scripts` не имеет +manifest, сохранённых hashes, кэша, plug-in system или fixture test harness. +Не вводятся изменения пользовательского HTTP-контракта, persisted production +data, внешний сетевой доступ или T-Bank credentials. diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/api-contract-generation/spec.md b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/api-contract-generation/spec.md new file mode 100644 index 0000000..a5d8af6 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/api-contract-generation/spec.md @@ -0,0 +1,38 @@ +## MODIFIED Requirements + +### Requirement: Frontend не зависит от OpenAPI во время работы +Работающий frontend MUST использовать ранее сгенерированные transport types и MUST NOT требовать доступность Swagger или OpenAPI endpoint во время запуска, выполнения пользовательского сценария или локальной проверки актуальности generated artifact. Доступ к backend OpenAPI endpoint разрешён только команде синхронизации snapshot и отдельной read-only runtime-contract command. + +#### Scenario: OpenAPI endpoint недоступен после сборки frontend +- **WHEN** frontend уже собран, а endpoint OpenAPI временно недоступен +- **THEN** frontend продолжает использовать сгенерированный код для запросов к API + +#### Scenario: Локальная проверка выполняется без backend +- **WHEN** разработчик запускает локальный frontend quality gate +- **THEN** проверка актуальности generated artifact использует сохранённый snapshot и не обращается к OpenAPI endpoint + +#### Scenario: Разработчик проверяет runtime-контракт +- **WHEN** разработчик запускает отдельную runtime-contract command +- **THEN** command может поднять изолированный local backend только для сравнения + его полного runtime OpenAPI document с committed frontend snapshot и не + обновляет ни один из артефактов + +### Requirement: Отдельная команда обнаруживает runtime drift OpenAPI +Runtime-contract command MUST получать полный OpenAPI document от изолированного +local backend по `/api/docs-json`, рекурсивно canonicalize порядок JSON object +keys, сохраняя каждое field и array order, и сравнивать его SHA-256 с canonical +committed frontend snapshot. При любом content mismatch она MUST завершаться +ошибкой и показывать diff. Verification MUST NOT вызывать snapshot +synchronization command или записывать committed OpenAPI либо generated type +artifacts. + +#### Scenario: Runtime success status отличается от committed snapshot +- **WHEN** backend runtime OpenAPI описывает response status, отличный от + committed frontend snapshot +- **THEN** runtime-contract command завершается ошибкой с diff и оставляет + snapshot и generated types без изменений + +#### Scenario: Runtime OpenAPI совпадает с committed snapshot +- **WHEN** каждое field canonical runtime document совпадает с canonical + committed frontend snapshot +- **THEN** runtime-contract stage завершается успешно без изменения frontend files diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/project-foundation/spec.md b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/project-foundation/spec.md new file mode 100644 index 0000000..7403485 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/specs/project-foundation/spec.md @@ -0,0 +1,27 @@ +## MODIFIED Requirements + +### Requirement: Репозитории содержат локальные инструкции для AI-агентов +Система SHALL содержать обзорный `AGENTS.md` в корне рабочей области и отдельный `AGENTS.md` в репозиториях frontend, backend, specs и docs. Инструкции MUST описывать назначение области, локальные команды проверки, границы ответственности и связь с SDD-артефактами. Каждая инструкция MUST объявлять `wealth-ledger-specs` и `specs/openspec` как canonical OpenSpec authority, требовать явный `--store wealth-ledger-specs` для поддерживаемых OpenSpec операций и запрещать versioned framework-specific planning artifacts вне canonical store. Локальные инструкции MUST направлять агента к durable remediation registry, когда задача связана с findings технического аудита. Backend instructions MUST различать non-mutating verification commands и явные auto-fix commands. + +#### Scenario: Агент начинает работу в отдельном репозитории +- **WHEN** агент открывает любой из репозиториев frontend, backend, specs или docs +- **THEN** он может определить локальные правила работы, canonical OpenSpec authority и источник связанных спецификаций из `AGENTS.md` этого репозитория + +#### Scenario: Агент получает audit-derived задачу из отдельного репозитория +- **WHEN** задача ссылается на finding технического аудита +- **THEN** локальный `AGENTS.md` указывает на remediation registry и canonical OpenSpec workflow с явным store selection до начала изменения кода + +#### Scenario: Агент выбирает backend quality gate +- **WHEN** агент читает backend instructions перед локальной проверкой +- **THEN** он запускает документированные non-mutating lint/typecheck commands и не получает auto-fix как неявный quality gate + +### Requirement: Локальный план MVP не является источником спецификации +Система SHALL содержать корневой локальный `MVP.md` с границами MVP, принятыми решениями, порядком поставок и критериями готовности. Требования к функциональности MUST храниться в OpenSpec, а устойчивая документация MUST храниться в репозитории docs. Frontend и backend MUST требовать Node 22 и документировать lockfile-safe bootstrap через `npm ci`. + +#### Scenario: Решение уточняется во время SDD-изменения +- **WHEN** изменение уточняет наблюдаемое поведение системы +- **THEN** оно фиксируется в OpenSpec и соответствующей документации, а `MVP.md` остается компактной картой поставки + +#### Scenario: Агент bootstrap-ит application repository +- **WHEN** агент начинает локальную проверку frontend или backend на чистой машине +- **THEN** он может определить Node 22 и выполнить `npm ci` без угадывания runtime policy diff --git a/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/tasks.md b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/tasks.md new file mode 100644 index 0000000..58c23a8 --- /dev/null +++ b/openspec/changes/archive/2026-08-23-make-local-verification-agent-safe/tasks.md @@ -0,0 +1,58 @@ +## 1. Зафиксировать активный и отложенный audit scope + +- [x] 1.1 Обновить `docs/development/audit-remediation.md`: пометить только + WL-P1-05, WL-P2-06, WL-P2-09 и WL-P2-11 как `active` с + `make-local-verification-agent-safe`; записать WL-P2-05 и WL-P2-13 как + `deferred` с датой, наблюдаемой причиной Prisma 7.9.1 и trigger review; + не помечать finding как `done`. +- [x] 1.2 Обновить durable development documentation и scoped `AGENTS.md`, + чтобы они называли только реально существующие non-mutating gates, Node 22 и + `npm ci`, а writers `lint:fix` и `openapi:sync` были явными. + +## 2. Восстановить non-mutating local gates backend + +- [x] 2.1 Добавить backend Node 22 runtime metadata и lockfile-safe bootstrap + guidance; разделить `lint:check`, `lint:fix` и совместимый non-mutating alias + `lint` в `backend/package.json`. +- [x] 2.2 Добавить отдельные backend TypeScript configurations и scripts для + приложения и тестов; application compilation исключает test sources, test + compilation включает test surface, оба выполняются без repository artifacts. +- [x] 2.3 Исправить существующий неполный `TbankClient` test double или + заменить его typed test adapter, чтобы test TypeScript gate проходил без + ослабления production client contract. +- [x] 2.4 Документировать, что текущие `test:e2e` и общий workspace verdict не + являются частью этого change, пока WL-P2-05/P2-13 отложены; не добавлять + обходной Prisma runner или ложный `npm run verify`. + +## 3. Проверить runtime API contract без writers + +- [x] 3.1 Реализовать минимальный runtime OpenAPI comparison helper, который + запускает backend с deterministic development test environment, ждёт local + readiness, получает `/api/docs-json`, canonicalizes object keys во временных + files, сравнивает SHA-256 полного document, выводит diff при mismatch и всегда + завершает child process. +- [x] 3.2 Подтвердить, что helper никогда не вызывает + `frontend/scripts/openapi-sync.mjs`, не обращается к external service, не + требует T-Bank credentials и не записывает committed snapshot/generated + types; запустить его для текущего contract и устранить каждый content mismatch + через согласованный contract workflow, а не скрытым переписыванием. + +## 4. Completion evidence и audit tracking + +- [x] 4.1 Запустить `openspec validate make-local-verification-agent-safe + --type change --strict --store wealth-ledger-specs`, `openspec validate + --specs --strict --store wealth-ledger-specs`, authority verifier, отдельный + runtime OpenAPI check и relevant per-repository checks; зафиксировать точные + commands и observed results. +- [x] 4.2 Выполнить independent read-only review diff, OpenSpec deltas, + temporary-resource behavior, runtime-contract comparison и точности + документации; устранить каждую confirmed issue и подтвердить, что этот task + list не содержит post-archive bookkeeping, commit или registry-completion + checkbox. +- [x] 4.3 Выполнить applicable pre-archive `opsx-verify` procedure и устранить + каждый blocking result. Эта задача завершается до archive. Только после + завершения каждого checkbox change может быть archived и synced; только после + этого remediation registry вне task list получает actual archive path, + commits, terminal evidence, четыре статуса `done` и ровно одну следующую + рекомендуемую задачу. WL-P2-05 и WL-P2-13 остаются `deferred` до своего + отдельного change. diff --git a/openspec/specs/api-contract-generation/spec.md b/openspec/specs/api-contract-generation/spec.md index 04e096c..f900cff 100644 --- a/openspec/specs/api-contract-generation/spec.md +++ b/openspec/specs/api-contract-generation/spec.md @@ -29,16 +29,42 @@ Frontend SHALL хранить в репозитории версионируем - **THEN** команда завершается с ошибкой и существующие snapshot и сгенерированные transport types не изменяются ### Requirement: Frontend не зависит от OpenAPI во время работы -Работающий frontend MUST использовать ранее сгенерированные transport types и MUST NOT требовать доступность Swagger или OpenAPI endpoint во время запуска, выполнения пользовательского сценария или локальной проверки актуальности generated artifact. Доступ к backend OpenAPI endpoint разрешён только команде синхронизации snapshot. +Работающий frontend MUST использовать ранее сгенерированные transport types и MUST NOT требовать доступность Swagger или OpenAPI endpoint во время запуска, выполнения пользовательского сценария или локальной проверки актуальности generated artifact. Доступ к backend OpenAPI endpoint разрешён только команде синхронизации snapshot и отдельной read-only runtime-contract command. #### Scenario: OpenAPI endpoint недоступен после сборки frontend - **WHEN** frontend уже собран, а endpoint OpenAPI временно недоступен - **THEN** frontend продолжает использовать сгенерированный код для запросов к API #### Scenario: Локальная проверка выполняется без backend -- **WHEN** разработчик запускает локальный quality gate без доступного backend +- **WHEN** разработчик запускает локальный frontend quality gate - **THEN** проверка актуальности generated artifact использует сохранённый snapshot и не обращается к OpenAPI endpoint +#### Scenario: Разработчик проверяет runtime-контракт +- **WHEN** разработчик запускает отдельную runtime-contract command +- **THEN** command может поднять изолированный local backend только для сравнения + его полного runtime OpenAPI document с committed frontend snapshot и не + обновляет ни один из артефактов + +### Requirement: Отдельная команда обнаруживает runtime drift OpenAPI +Runtime-contract command MUST получать полный OpenAPI document от изолированного +local backend по `/api/docs-json`, рекурсивно canonicalize порядок JSON object +keys, сохраняя каждое field и array order, и сравнивать его SHA-256 с canonical +committed frontend snapshot. При любом content mismatch она MUST завершаться +ошибкой и показывать diff. Verification MUST NOT вызывать snapshot +synchronization command или записывать committed OpenAPI либо generated type +artifacts. + +#### Scenario: Runtime success status отличается от committed snapshot +- **WHEN** backend runtime OpenAPI описывает response status, отличный от + committed frontend snapshot +- **THEN** runtime-contract command завершается ошибкой с diff и оставляет + snapshot и generated types без изменений + +#### Scenario: Runtime OpenAPI совпадает с committed snapshot +- **WHEN** каждое field canonical runtime document совпадает с canonical + committed frontend snapshot +- **THEN** runtime-contract stage завершается успешно без изменения frontend files + ### Requirement: Frontend обнаруживает дрейф сгенерированного контракта Frontend SHALL проверять, что committed generated transport types соответствуют versioned OpenAPI snapshot, и MUST завершать quality gate с ошибкой при расхождении. diff --git a/openspec/specs/project-foundation/spec.md b/openspec/specs/project-foundation/spec.md index cc7d7dd..4508c93 100644 --- a/openspec/specs/project-foundation/spec.md +++ b/openspec/specs/project-foundation/spec.md @@ -7,7 +7,7 @@ ## Requirements ### Requirement: Репозитории содержат локальные инструкции для AI-агентов -Система SHALL содержать обзорный `AGENTS.md` в корне рабочей области и отдельный `AGENTS.md` в репозиториях frontend, backend, specs и docs. Инструкции MUST описывать назначение области, локальные команды проверки, границы ответственности и связь с SDD-артефактами. Каждая инструкция MUST объявлять `wealth-ledger-specs` и `specs/openspec` как canonical OpenSpec authority, требовать явный `--store wealth-ledger-specs` для поддерживаемых OpenSpec операций и запрещать versioned framework-specific planning artifacts вне canonical store. Локальные инструкции MUST направлять агента к durable remediation registry, когда задача связана с findings технического аудита. +Система SHALL содержать обзорный `AGENTS.md` в корне рабочей области и отдельный `AGENTS.md` в репозиториях frontend, backend, specs и docs. Инструкции MUST описывать назначение области, локальные команды проверки, границы ответственности и связь с SDD-артефактами. Каждая инструкция MUST объявлять `wealth-ledger-specs` и `specs/openspec` как canonical OpenSpec authority, требовать явный `--store wealth-ledger-specs` для поддерживаемых OpenSpec операций и запрещать versioned framework-specific planning artifacts вне canonical store. Локальные инструкции MUST направлять агента к durable remediation registry, когда задача связана с findings технического аудита. Backend instructions MUST различать non-mutating verification commands и явные auto-fix commands. #### Scenario: Агент начинает работу в отдельном репозитории - **WHEN** агент открывает любой из репозиториев frontend, backend, specs или docs @@ -17,13 +17,21 @@ - **WHEN** задача ссылается на finding технического аудита - **THEN** локальный `AGENTS.md` указывает на remediation registry и canonical OpenSpec workflow с явным store selection до начала изменения кода +#### Scenario: Агент выбирает backend quality gate +- **WHEN** агент читает backend instructions перед локальной проверкой +- **THEN** он запускает документированные non-mutating lint/typecheck commands и не получает auto-fix как неявный quality gate + ### Requirement: Локальный план MVP не является источником спецификации -Система SHALL содержать корневой локальный `MVP.md` с границами MVP, принятыми решениями, порядком поставок и критериями готовности. Требования к функциональности MUST храниться в OpenSpec, а устойчивая документация MUST храниться в репозитории docs. +Система SHALL содержать корневой локальный `MVP.md` с границами MVP, принятыми решениями, порядком поставок и критериями готовности. Требования к функциональности MUST храниться в OpenSpec, а устойчивая документация MUST храниться в репозитории docs. Frontend и backend MUST требовать Node 22 и документировать lockfile-safe bootstrap через `npm ci`. #### Scenario: Решение уточняется во время SDD-изменения - **WHEN** изменение уточняет наблюдаемое поведение системы - **THEN** оно фиксируется в OpenSpec и соответствующей документации, а `MVP.md` остается компактной картой поставки +#### Scenario: Агент bootstrap-ит application repository +- **WHEN** агент начинает локальную проверку frontend или backend на чистой машине +- **THEN** он может определить Node 22 и выполнить `npm ci` без угадывания runtime policy + ### Requirement: Frontend предоставляет минимальный каркас приложения Frontend SHALL предоставлять русскоязычный адаптивный каркас приложения с маршрутами `/login`, `/register`, `/accounts`, `/accounts/:accountId` и `/settings`, светлой финансовой темой и навигацией для счетов и настроек. Каркас MUST содержать состояния загрузки, ошибки и пустых данных без реализации доменных сценариев. diff --git a/scripts/verify-runtime-openapi.sh b/scripts/verify-runtime-openapi.sh new file mode 100755 index 0000000..2e086ce --- /dev/null +++ b/scripts/verify-runtime-openapi.sh @@ -0,0 +1,126 @@ +#!/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."