From b81afaf59aecf21209420c463d9ae9f4dae4cf19 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 25 Oct 2026 11:06:28 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20=D1=81=D0=BF=D0=B5=D0=BA?= =?UTF-8?q?=D0=B0=20approval-gates=20=E2=80=94=20request=5Fapproval=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BA=20=D0=BB=D0=B5=D0=B3=D0=B8=D1=82=D0=B8=D0=BC?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=B8=D1=81=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=20(T1.1,=20#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openspec/specs/approval-gates/spec.md | 95 +++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/openspec/specs/approval-gates/spec.md b/openspec/specs/approval-gates/spec.md index 9bca38d..4d68763 100644 --- a/openspec/specs/approval-gates/spec.md +++ b/openspec/specs/approval-gates/spec.md @@ -16,9 +16,11 @@ builder-check субагента, скопированный в change контроллером; он MUST быть записан после builder-check и до передачи дизайна владельцу. -`openspec/changes//approval.md` SHALL быть маркером утверждения владельцем; -его единственный писатель — команда `/approve` (D-1), инструменты модели писать -его MUST NOT (гейт C ниже). +`openspec/changes//approval.md` SHALL быть маркером утверждения владельцем. +Писать его MUST только код бандла — команда `/approve` или инструмент +`request_approval` (D-1), и оба MUST писать только после подтверждения владельца. +Прямые записи модели (`write`, `edit`, `multi_edit`, `str_replace_editor`, `bash`) +MUST NOT создавать или менять этот файл (гейт C ниже, без изменений). Формат `approval.md` — yaml с секциями `design:` (поля `by`, `at`) и `plan:` (поля `by`, `at`, `sha256`); `sha256` — hex-хэш sha256 содержимого файла плана на @@ -42,11 +44,14 @@ builder-check субагента, скопированный в change конт ### Requirement: Команда /approve -`/approve design|plan` SHALL быть единственным писателем -`approval.md` и MUST регистрироваться при `enabled` и `approvalGates: true`, -независимо от флагов `roleCommand` и `checkCommand`: иначе гейты остаются без -команды-разблокировки (T3.2). `change-id` MUST быть одним безопасным сегментом -пути; небезопасный id или неизвестный вид — usage-отказ без записи. +`/approve design|plan` SHALL быть ручным писателем `approval.md` — +одним из двух писателей бандла, наравне с инструментом `request_approval` +(следующее требование); оба пишут только после подтверждения владельца, прямые +записи модели — MUST NOT (гейт C). Команда MUST регистрироваться при `enabled` и +`approvalGates: true`, независимо от флагов `roleCommand` и `checkCommand`: иначе +гейты остаются без команды-разблокировки (T3.2). `change-id` MUST быть одним +безопасным сегментом пути; небезопасный id или неизвестный вид — usage-отказ без +записи. Вид `design` SHALL требовать существования `proposal.md`, `design.md` и `builder-check.md` в `openspec/changes//`; при отсутствии файла команда @@ -93,6 +98,71 @@ MUST отказать с перечислением отсутствующих - **THEN** команда отказывает с `userQuestions service unavailable; /approve requires it` и `approval.md` не пишется +### Requirement: Инструмент request_approval + +Инструмент модели `request_approval` SHALL быть вторым писателем `approval.md`: +модель инициирует утверждение, владелец только подтверждает в диалоге, а маркер +пишет код бандла (`lib/approval.js`) — с теми же проверками предпосылок и той же +записью, что у `/approve`. Инструмент MUST регистрироваться при `enabled` и +`approvalGates: true`, если в контексте доступен `ctx.tools.register`; +`userQuestions` MUST резолвиться лениво (не через `inject`), чтобы отсутствие +сервиса не парковало весь бандл. + +Параметры инструмента — `change_id` (строка) и `kind` (`design` | `plan`). +Валидация MUST быть собственной, в инструменте: пустой или небезопасный +`change_id` и неизвестный `kind` — usage-отказ без записи и без вопроса +владельцу; проверки предпосылок и отказы те же, что у `/approve` (design — +proposal/design/builder-check, plan — утверждённый дизайн, ровно один план, +sha256), и любой отказ MUST NOT писать `approval.md`. Подтверждением SHALL +считаться только ответ `answers[0].selected[0] === 'Approve'`; Cancel, +отсутствие ответа и ошибка диалога — отказ `not confirmed` без записи. При +отсутствии сервиса `userQuestions` инструмент MUST отказать (`userQuestions +service unavailable`), а не писать. + +#### Scenario: Модель просит утвердить дизайн + +- **WHEN** модель вызывает `request_approval` с `{change_id: , kind: design}`, + предпосылки выполнены, а владелец выбрал Approve +- **THEN** в `approval.md` пишется design-секция (`by: owner`, `at` в ISO 8601 + UTC), инструмент возвращает `{approved: true, message}` + +#### Scenario: Модель просит утвердить план + +- **WHEN** модель вызывает `request_approval` с `{change_id: , kind: plan}`, + дизайн утверждён, план единственный, владелец выбрал Approve +- **THEN** plan-секция содержит `by: owner`, `at` в ISO 8601 и sha256 текущего + содержимого плана + +#### Scenario: Владелец отменил утверждение + +- **WHEN** в диалоге `request_approval` владелец выбрал Cancel +- **THEN** инструмент возвращает `{approved: false, message: 'not confirmed'}`, а + `approval.md` не меняется + +#### Scenario: Некорректные аргументы request_approval + +- **WHEN** модель передала неизвестный `kind` или небезопасный `change_id` + (например, `../x`) +- **THEN** инструмент возвращает usage-отказ без записи, владелец не спрашивается + +#### Scenario: Отсутствует builder-check при утверждении дизайна моделью + +- **WHEN** `kind: design`, а `builder-check.md` в change нет +- **THEN** инструмент отказывает с перечислением отсутствующего файла, владелец не + спрашивается, `approval.md` не создаётся + +#### Scenario: request_approval plan без утверждённого дизайна + +- **WHEN** `kind: plan`, а design-секции нет +- **THEN** инструмент отказывает `design not approved; run /approve design`, + владелец не спрашивается, `approval.md` не меняется + +#### Scenario: request_approval без сервиса вопросов + +- **WHEN** `userQuestions` отсутствует +- **THEN** инструмент возвращает `{approved: false}` с `userQuestions service + unavailable`, владелец не спрашивается, `approval.md` не пишется + ### Requirement: Гейт A — план до утверждения дизайна Для write/edit/multi_edit/`str_replace_editor` с целевым путём под @@ -231,9 +301,12 @@ fail closed. Фрагмент `edit` проверяется по своему `n ### Requirement: Гейт C и bash-обход -`approval.md` MUST писаться только командой `/approve`: любой write/edit с целью, -чей basename — `approval.md`, и любая bash-цель с таким именем MUST получать -отказ `approval.md is written only by /approve` (D-1). +`approval.md` MUST писаться только кодом бандла — командой `/approve` или +инструментом `request_approval`: любой write/edit с целью, чей basename — +`approval.md`, и любая bash-цель с таким именем MUST получать отказ `approval.md +is written only by /approve` (D-1). Deny-текст не меняется: он адресован прямым +записям модели (`write`/`edit`/`bash`), а `request_approval` пишет маркер кодом +бандла и под этот запрет не попадает. Bash-обход MUST быть закрыт (D-8): если команда содержит грубую подстроку `docs/superpowers/plans`, `approval.md` или `.superpowers/sdd`, её файловые цели