ADR 0102 — Пересмотр ADR 0065: граница внешнего эффекта перестаёт быть deny и становится абсолютным пределом
- Статус: Принят
- Дата: 2026-08-20
- Контекст: ГЛОБАЛЬНОЕ решение владельца продукта о политике git/sudo, RN-418 (пересматривает RN-106)
- Пересматривает: ADR 0065
Контекст
ADR 0065 решил в своё время, что
git push, открытие PR и deploy никогда не выходят через терминал — правилом
был безусловный deny, применяемый ДО любой разрешающей стадии
в decide(), с сообщением, перенаправляющим на ТИПИЗИРОВАННОЕ действие
(git_push/git_merge/pr_open). Объявленная причина deny (вместо
require_approval) была конкретной: существовало "всегда разрешать", и один
клик записал бы шаблон в allow в permissions.json, открывая
дверь навсегда — отказ с самого начала был единственным способом гарантировать, что
дверь никогда не откроется.
Владелец продукта ЯВНО и ГЛОБАЛЬНО попросил изменить
семантику: sudo/doas и команда терминала с внешним эффектом git
должны ВСЕГДА запрашивать человеческое разрешение — никогда не автоодобряться, даже при
включённом "автоматическом режиме", — а любая ДРУГАЯ команда должна автоодобряться,
когда автоматический режим включён. Это отличается от deny: это
"всегда становится ожидающим proposed_action, решаемым в каждом случае", а не
"всегда отклоняется, ничем не становясь".
Автоматическая система безопасности отметила это изменение во время
реализации (изменение правила, которое сам CLAUDE.md описывал
как абсолютный deny, заслуживает удвоенного внимания), — владелец продукта
явно подтвердил, после просмотра изменения, что решение было
именно таким.
Решение
git push/открытие PR/deploy (RN-106, пересмотрено) и sudo/doas
(новое) уходят из блока границы (который стоял сразу после IAM,
возвращая deny) и становятся АБСОЛЮТНЫМ ПРЕДЕЛОМ — в ТОМ ЖЕ финальном блоке, где уже
живут остальные пределы (защищённый merge, instruction_patch,
parallelize/raise_max_parallel, область путей), по ТОМУ ЖЕ шаблону
кода (current.policy === 'auto_approve' → перезаписывает на
require_approval). По построению предел действует, даже если
agent_autonomy говорит auto_approve для подстановочного знака "*", и даже если
в permissions.json есть запись allow, которая совпала бы.
Щель "всегда разрешать" закрыта В ИСТОЧНИКЕ — необходимое условие
безопасности этого ADR: ApproveAlwaysActionUseCase/
patternForAction ОТКАЗЫВАЮТСЯ записывать шаблон в allow для действия терминала
с внешним эффектом git или привилегированной командой. Пользователь по-прежнему может
одобрить КОНКРЕТНЫЙ ЭКЗЕМПЛЯР обычным потоком одобрения — лишь
"всегда разрешать" (который записал бы навсегда) отклоняется, с ясным
сообщением, объясняющим почему. Без этой второй половины абсолютный предел становится
декоративным — одного клика хватило бы, чтобы снова открыть дверь, которую он якобы закрывает.
Это ровно исходный аргумент ADR 0065 в пользу deny, только решённый
у источника вместо блокировки симптома.
sudo/doas получают собственную категорию в external-effect.ts
(comandoPrivilegiadoNoComando), совпадающую по ГЛАГОЛУ в любом сегменте
команды (тот же принцип, что efeitoExternoNoComando для git). У них нет
эквивалентного типизированного действия для перенаправления — сообщение лишь объясняет, почему
эта команда требует человеческого решения.
Последствия
require_approvalздесь не "слабее"denyв смысле "агент может сделать сам" — ни в ОДНОМ из двух состояний команда не выполняется без явного человеческого решения. Разница чисто в МЕХАНИЗМЕ: раньше путь через терминал для git был заблокирован, и существовало лишь типизированное действие (которое и так всегда требовало одобрения); теперь и сам путь через терминал может стать ожидающимproposed_action, проверяемым в event log, решаемым в каждом случае, — в согласии с остальным продуктом, который предпочитает ожидающее и прослеживаемое действие немому отказу.- Это ЧЕТВЁРТАЯ/ПЯТАЯ строка мерила абсолютных пределов, которые
decide()применяет безусловно, — CLAUDE.md и документация соглашений отныне должны считать этот предел вместе с остальными. - Механизм локального runner (ADR 0103) — самый прямой потребитель этого
изменения: без него легитимный
sudoна машине пользователя попал бы в общийrequire_approval(без гарантии никогда не стать автоодобряемым, еслиpermissions.json/auto mode однажды по небрежности покрыли бы этот глагол), — абсолютный предел закрывает этот риск по построению, а не по соглашению.