Перейти к основному содержимому

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 однажды по небрежности покрыли бы этот глагол), — абсолютный предел закрывает этот риск по построению, а не по соглашению.