docs: спека approval-gates — точные формулировки гейтов A/B и frozen-планов (T4.1-T4.3, #59)
This commit is contained in:
parent
734c80899d
commit
7e3b86b59a
@ -209,10 +209,12 @@ repository layout:
|
|||||||
enable them. They are active only in a session with the reinstalled bundle
|
enable them. They are active only in a session with the reinstalled bundle
|
||||||
(D-13): an old install keeps the text-only loop, and the `sdd` skill checks the
|
(D-13): an old install keeps the text-only loop, and the `sdd` skill checks the
|
||||||
plan approval by hand.
|
plan approval by hand.
|
||||||
- Plan files are always rewritten in full: an `edit` fragment cannot carry the
|
- Gate A is content-based: it reads `**Spec:**` from `content` / `new_string`.
|
||||||
`**Spec:**` line, so gate A denies it — as it denies bash plan targets and
|
An `edit` fragment is checked against its own text, so a fragment carrying a
|
||||||
`str_replace_editor` creates, whose content it cannot see. The deny text then
|
`**Spec:**` line for an approved change passes, while the usual Spec-less
|
||||||
names the missing `**Spec:**`, not the real reason.
|
fragment is denied (rewrite the plan file in full). Bash plan targets and
|
||||||
|
`str_replace_editor` creates are denied because the gate cannot see their
|
||||||
|
content; the deny text then names the missing `**Spec:**`, not the real reason.
|
||||||
- `terminal_open` / `terminal_send` write files outside both the gates and the
|
- `terminal_open` / `terminal_send` write files outside both the gates and the
|
||||||
bash hooks — a residual risk not covered by the port.
|
bash hooks — a residual risk not covered by the port.
|
||||||
- `bundle/hooks` is a symlink for local-directory installation. A published
|
- `bundle/hooks` is a symlink for local-directory installation. A published
|
||||||
|
|||||||
@ -46,7 +46,8 @@ plan:
|
|||||||
`**Spec:**` или со Spec-ссылкой вне `openspec/changes/` (legacy
|
`**Spec:**` или со Spec-ссылкой вне `openspec/changes/` (legacy
|
||||||
`docs/superpowers/specs/`) — DENY «plan must carry **Spec:** pointing to
|
`docs/superpowers/specs/`) — DENY «plan must carry **Spec:** pointing to
|
||||||
openspec/changes/<id>» (D-9); правка без содержимого (bash-цель,
|
openspec/changes/<id>» (D-9); правка без содержимого (bash-цель,
|
||||||
edit-фрагмент) тоже DENY — гейт не видит содержимого.
|
`str_replace_editor` create) и фрагмент `edit` без `**Spec:**` тоже DENY —
|
||||||
|
гейт проверяет только то содержимое, которое видит.
|
||||||
2. **Гейт B (sdd до approve плана).** Два входа (D-7): (a) запись в
|
2. **Гейт B (sdd до approve плана).** Два входа (D-7): (a) запись в
|
||||||
`.superpowers/sdd/<план>/**` (ledger, брифы — старт sdd в Режиме 1) и
|
`.superpowers/sdd/<план>/**` (ledger, брифы — старт sdd в Режиме 1) и
|
||||||
(b) диспатч `subagent`/`subagent_fork`/`workflow`, в промпте или
|
(b) диспатч `subagent`/`subagent_fork`/`workflow`, в промпте или
|
||||||
@ -66,10 +67,11 @@ plan:
|
|||||||
AskUserQuestion-подтверждение: `/approve` спрашивает подтверждение
|
AskUserQuestion-подтверждение: `/approve` спрашивает подтверждение
|
||||||
перед записью, ответ «да» = владелец).
|
перед записью, ответ «да» = владелец).
|
||||||
|
|
||||||
Исторические планы: четыре файла — `2026-10-11-roles-and-memory`,
|
Исторические планы: четыре файла написаны до лупа (T1.3, T4.3) —
|
||||||
`2026-10-24-dsh-decouple`, `2026-10-24-dsh-port-spike`,
|
`2026-10-11-roles-and-memory` несёт legacy-`**Spec:**` в
|
||||||
`2026-10-24-dsh-role-loader` — написаны до лупа и не несут `**Spec:**`
|
`docs/superpowers/specs/`, а `2026-10-24-dsh-decouple`,
|
||||||
(T1.3). По D-9 любая их правка — DENY: они заморожены, новая работа идёт
|
`2026-10-24-dsh-port-spike` и `2026-10-24-dsh-role-loader` — без `**Spec:**`.
|
||||||
|
По D-9 запись в любой из них — DENY: они заморожены, новая работа идёт
|
||||||
отдельным планом. Это ожидаемое поведение, а не регресс.
|
отдельным планом. Это ожидаемое поведение, а не регресс.
|
||||||
|
|
||||||
## Decisions made without discussion
|
## Decisions made without discussion
|
||||||
|
|||||||
@ -101,19 +101,22 @@ MUST отказать с перечислением отсутствующих
|
|||||||
(`content` или `new_string`). Ссылка MUST указывать в `openspec/changes/<id>`;
|
(`content` или `new_string`). Ссылка MUST указывать в `openspec/changes/<id>`;
|
||||||
план без `**Spec:**` и план с legacy-ссылкой в `docs/superpowers/specs/**` MUST
|
план без `**Spec:**` и план с legacy-ссылкой в `docs/superpowers/specs/**` MUST
|
||||||
получать отказ `plan must carry **Spec:** pointing to openspec/changes/<id>`
|
получать отказ `plan must carry **Spec:** pointing to openspec/changes/<id>`
|
||||||
(D-9). Правка без содержимого (bash-цель, edit-фрагмент, `str_replace_editor`
|
(D-9). Правка без содержимого (bash-цель, `str_replace_editor` create) MUST
|
||||||
create) MUST получать тот же отказ: гейт не видит содержимого и не может
|
получать тот же отказ: гейт не видит содержимого и не может подтвердить Spec —
|
||||||
подтвердить Spec — fail closed.
|
fail closed. Фрагмент `edit` проверяется по своему `new_string` (content-based):
|
||||||
|
фрагмент, который сам несёт `**Spec:**` на утверждённый change, проходит; фрагмент
|
||||||
|
без Spec, с legacy-ссылкой или с неутверждённым change — DENY.
|
||||||
|
|
||||||
Если change определён, а его design-секция не является утверждением, гейт MUST
|
Если change определён, а его design-секция не является утверждением, гейт MUST
|
||||||
отказать `design not approved; run /approve <id> design`. Утверждённый дизайн
|
отказать `design not approved; run /approve <id> design`. Утверждённый дизайн
|
||||||
разрешает запись плана; запись вне `docs/superpowers/plans/` гейтом A не
|
разрешает запись плана; запись вне `docs/superpowers/plans/` гейтом A не
|
||||||
проверяется.
|
проверяется.
|
||||||
|
|
||||||
Исторические планы, написанные до лупа и не несущие `**Spec:**`, заморожены —
|
Исторические планы заморожены — все написаны до лупа: четыре файла.
|
||||||
четыре файла: `2026-10-11-roles-and-memory`, `2026-10-24-dsh-decouple`,
|
`2026-10-11-roles-and-memory` несёт legacy-`**Spec:**` в
|
||||||
`2026-10-24-dsh-port-spike`, `2026-10-24-dsh-role-loader` (T1.3). Любая их правка
|
`docs/superpowers/specs/`; `2026-10-24-dsh-decouple`, `2026-10-24-dsh-port-spike`
|
||||||
MUST получать отказ по D-9; это ожидаемое поведение, а не дефект.
|
и `2026-10-24-dsh-role-loader` — без `**Spec:**` (T1.3, T4.3). Любая запись в
|
||||||
|
любой из них MUST получать отказ по D-9; это ожидаемое поведение, а не дефект.
|
||||||
|
|
||||||
#### Scenario: План со Spec на неутверждённый дизайн
|
#### Scenario: План со Spec на неутверждённый дизайн
|
||||||
|
|
||||||
@ -133,16 +136,30 @@ MUST получать отказ по D-9; это ожидаемое повед
|
|||||||
на этот change
|
на этот change
|
||||||
- **THEN** запись плана разрешена
|
- **THEN** запись плана разрешена
|
||||||
|
|
||||||
#### Scenario: Правка плана фрагментом
|
#### Scenario: Правка плана фрагментом без Spec
|
||||||
|
|
||||||
- **WHEN** edit плана передаёт `new_string` без `**Spec:**` (фрагмент правки)
|
- **WHEN** edit плана передаёт `new_string` без `**Spec:**` (обычный фрагмент
|
||||||
- **THEN** отказ: план пишется только full-file write
|
правки)
|
||||||
|
- **THEN** отказ `plan must carry **Spec:** …`: гейт видит только фрагмент, план
|
||||||
|
целиком пишется full-file write
|
||||||
|
|
||||||
|
#### Scenario: Фрагмент edit со Spec на утверждённый change
|
||||||
|
|
||||||
|
- **WHEN** edit плана передаёт `new_string`, в котором есть `**Spec:**` на change
|
||||||
|
с непустой design-секцией
|
||||||
|
- **THEN** запись разрешена: гейт проверяет содержимое, а не инструмент
|
||||||
|
|
||||||
#### Scenario: Правка исторического плана
|
#### Scenario: Правка исторического плана
|
||||||
|
|
||||||
- **WHEN** write в `docs/superpowers/plans/2026-10-24-dsh-decouple.md`
|
- **WHEN** write в `docs/superpowers/plans/2026-10-24-dsh-decouple.md`
|
||||||
- **THEN** отказ `plan must carry **Spec:** …` по D-9
|
- **THEN** отказ `plan must carry **Spec:** …` по D-9
|
||||||
|
|
||||||
|
#### Scenario: Правка плана с legacy-Spec
|
||||||
|
|
||||||
|
- **WHEN** write в `docs/superpowers/plans/2026-10-11-roles-and-memory.md`, чей
|
||||||
|
`**Spec:**` указывает в `docs/superpowers/specs/`
|
||||||
|
- **THEN** отказ `plan must carry **Spec:** …` по D-9
|
||||||
|
|
||||||
### Requirement: Гейт B — sdd до утверждения плана
|
### Requirement: Гейт B — sdd до утверждения плана
|
||||||
|
|
||||||
Гейт SHALL срабатывать на двух входах (D-7): (a) write в
|
Гейт SHALL срабатывать на двух входах (D-7): (a) write в
|
||||||
@ -154,12 +171,22 @@ MUST получать отказ по D-9; это ожидаемое повед
|
|||||||
Для каждого входа гейт MUST определить файл
|
Для каждого входа гейт MUST определить файл
|
||||||
`docs/superpowers/plans/<plan-name>.md`, взять из него `**Spec:**` → change и
|
`docs/superpowers/plans/<plan-name>.md`, взять из него `**Spec:**` → change и
|
||||||
потребовать plan-секцию `approval.md` с sha256, равным sha256 текущего
|
потребовать plan-секцию `approval.md` с sha256, равным sha256 текущего
|
||||||
содержимого плана. Нет секции или sha256 — отказ `plan not approved; run
|
содержимого плана. Отказы MUST различаться по причине:
|
||||||
/approve <id> plan`; несовпадение хэша или нечитаемый план — отказ `plan changed
|
|
||||||
after approval; re-approve`; нет файла плана для sdd-каталога — отказ `plan not
|
- (a) файл плана по ссылке отсутствует или не читается — `plan not approved; run
|
||||||
found: <plan-name>`; план без `**Spec:**` — отказ `plan not approved` (id
|
/approve <id> plan` (fail closed; id остаётся литеральным `<id>`, если план не
|
||||||
неизвестен). Проверяться MUST все план-ссылки, а не первая; возвращается первый
|
прочитан);
|
||||||
отказ, чтобы утверждённый план не маскировал неутверждённый.
|
- (b) план читается, но без `**Spec:**` или с legacy-ссылкой вне
|
||||||
|
`openspec/changes/` — тот же `plan not approved; run /approve <id> plan`;
|
||||||
|
- (c) план и change определены, но plan-секции нет или в ней нет sha256 — тот же
|
||||||
|
`plan not approved; run /approve <id> plan`, уже с id change;
|
||||||
|
- (d) plan-секция есть, но sha256 не совпадает с текущим содержимым плана —
|
||||||
|
`plan changed after approval; re-approve`.
|
||||||
|
|
||||||
|
Для входа через `.superpowers/sdd/<plan-name>/` без файла
|
||||||
|
`docs/superpowers/plans/<plan-name>.md` отказ — `plan not found: <plan-name>`.
|
||||||
|
Проверяться MUST все план-ссылки, а не первая; возвращается первый отказ, чтобы
|
||||||
|
утверждённый план не маскировал неутверждённый.
|
||||||
|
|
||||||
Вызов без план-ссылки MUST NOT гейтиться. Разбор промпта — эвристика по
|
Вызов без план-ссылки MUST NOT гейтиться. Разбор промпта — эвристика по
|
||||||
подстрокам, поэтому разбор fail open: вызов, в котором ссылка не распознана, не
|
подстрокам, поэтому разбор fail open: вызов, в котором ссылка не распознана, не
|
||||||
@ -185,6 +212,13 @@ found: <plan-name>`; план без `**Spec:**` — отказ `plan not approv
|
|||||||
- **WHEN** промпт `subagent` ссылается на два плана, и второй не утверждён
|
- **WHEN** промпт `subagent` ссылается на два плана, и второй не утверждён
|
||||||
- **THEN** диспатч отклонён по второму плану, даже если первый утверждён
|
- **THEN** диспатч отклонён по второму плану, даже если первый утверждён
|
||||||
|
|
||||||
|
#### Scenario: План по ссылке не найден
|
||||||
|
|
||||||
|
- **WHEN** `subagent` получает промпт со ссылкой на
|
||||||
|
`docs/superpowers/plans/missing.md`, которого нет
|
||||||
|
- **THEN** отказ `plan not approved; run /approve <id> plan` (fail closed), а не
|
||||||
|
`plan changed after approval`
|
||||||
|
|
||||||
#### Scenario: План изменён после утверждения
|
#### Scenario: План изменён после утверждения
|
||||||
|
|
||||||
- **WHEN** содержимое плана изменилось после `/approve <id> plan`
|
- **WHEN** содержимое плана изменилось после `/approve <id> plan`
|
||||||
@ -245,9 +279,11 @@ Bounded-задача без change и плана MUST NOT гейтиться (D-
|
|||||||
бандлом (D-13): до переустановки их нет, и sdd-скилл делает ручную проверку
|
бандлом (D-13): до переустановки их нет, и sdd-скилл делает ручную проверку
|
||||||
маркера; после — её дублирует enforcement.
|
маркера; после — её дублирует enforcement.
|
||||||
|
|
||||||
Правка плана фрагментом (`edit`) MUST всегда получать отказ, план пишется
|
Правка плана фрагментом (`edit`) обычно получает отказ: гейт проверяет только
|
||||||
full-file write (T3.5). Bash-цели и `str_replace_editor` create с невидимым
|
`new_string`, а обычный фрагмент не несёт `**Spec:**`, поэтому план целиком пишется
|
||||||
содержимым тоже отказывают; deny-текст про отсутствующий `**Spec:**` для них
|
full-file write; фрагмент со своей строкой `**Spec:**` на утверждённый change
|
||||||
|
проходит (T3.5, T4.1). Bash-цели и `str_replace_editor` create с невидимым
|
||||||
|
содержимым отказывают всегда; deny-текст про отсутствующий `**Spec:**` для них
|
||||||
вводит в заблуждение — реальная причина в невидимом содержимом (T3.4).
|
вводит в заблуждение — реальная причина в невидимом содержимом (T3.4).
|
||||||
|
|
||||||
`terminal_open` / `terminal_send` MUST оставаться вне гейтов: они пишут файлы в
|
`terminal_open` / `terminal_send` MUST оставаться вне гейтов: они пишут файлы в
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user