docs(openspec): спека approval-gates — request_approval как легитимный писатель (T1.1, #77)
This commit is contained in:
parent
1723806e16
commit
b81afaf59a
@ -16,9 +16,11 @@
|
||||
builder-check субагента, скопированный в change контроллером; он MUST быть записан
|
||||
после builder-check и до передачи дизайна владельцу.
|
||||
|
||||
`openspec/changes/<id>/approval.md` SHALL быть маркером утверждения владельцем;
|
||||
его единственный писатель — команда `/approve` (D-1), инструменты модели писать
|
||||
его MUST NOT (гейт C ниже).
|
||||
`openspec/changes/<id>/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 <change-id> design|plan` SHALL быть единственным писателем
|
||||
`approval.md` и MUST регистрироваться при `enabled` и `approvalGates: true`,
|
||||
независимо от флагов `roleCommand` и `checkCommand`: иначе гейты остаются без
|
||||
команды-разблокировки (T3.2). `change-id` MUST быть одним безопасным сегментом
|
||||
пути; небезопасный id или неизвестный вид — usage-отказ без записи.
|
||||
`/approve <change-id> 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/<id>/`; при отсутствии файла команда
|
||||
@ -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: <id>, kind: design}`,
|
||||
предпосылки выполнены, а владелец выбрал Approve
|
||||
- **THEN** в `approval.md` пишется design-секция (`by: owner`, `at` в ISO 8601
|
||||
UTC), инструмент возвращает `{approved: true, message}`
|
||||
|
||||
#### Scenario: Модель просит утвердить план
|
||||
|
||||
- **WHEN** модель вызывает `request_approval` с `{change_id: <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 <id> 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`, её файловые цели
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user