Execpolicy - это третий механизм доверия рядом с sandbox и approvals, и он про конкретные команды. Rules управляют префиксами команд, которые Codex хочет выполнить вне sandbox: они детерминированно решают судьбу вызова по его началу, а не полагаются на суждение модели. Важная оговорка о зрелости: формат экспериментальный и использует язык Starlark. Это значит, что синтаксис может меняться, и его сверяют с текущей документацией, а не переносят по памяти.
Расположение rule-файла подчиняется той же логике слоёв, что и остальная конфигурация. Файл лежит в каталоге rules/ рядом с активным слоем config - например, ~/.codex/rules/default.rules для пользовательского уровня или .codex/rules/ в доверенном проекте. Это важно для модели доверия: правила из проекта действуют только после того, как workspace доверен, ровно как и остальная исполняемая конфигурация. Расположение определяет и приоритет, и то, кем правило написано.
Правило описывают через префикс команды и решение, а не через полную строку. Полезно один раз увидеть такое правило целиком. Ниже - prefix_rule для чтения через gh pr: паттерн задаёт начало команды, decision - что с ней делать (здесь prompt), justification объясняет, зачем нужна остановка. Именно префикс, а не точная строка, позволяет одному правилу покрыть семейство команд, оставаясь при этом узким и предсказуемым.
Ключевое в правиле - это встроенные unit-тесты через match и not_match. Поля match перечисляют команды, которые правило обязано покрыть, а not_match - те, которые оно не должно задеть. Это не украшение, а проверка: список not_match ловит слишком широкий паттерн, который случайно захватил бы соседнюю команду. Правило без тестов легко написать мимо намерения; правило с match и not_match само доказывает, что покрывает именно то, что задумано, и ничего сверх.
Решения упорядочены по строгости, и этот порядок нельзя обойти. forbidden сильнее prompt, а prompt сильнее allow: если команда попадает под несколько правил, побеждает более строгое. Это та же логика, что и deny в permissions, - запрет не снимается более мягким разрешением. Понимание порядка строгости избавляет от иллюзии, что широкий allow "перекроет" забытый forbidden: на деле именно строгое решение определяет судьбу команды.
Полезно один раз увидеть, как правило проверяют до применения. Ниже - codex execpolicy check: он прогоняет конкретную команду против набора правил и показывает решение. Это негативный тест политики в чистом виде: вместо того чтобы узнать о слишком широком или слишком узком правиле в бою, вы проверяете его заранее на реальных командах. Проверять execpolicy check'ом стоит каждое изменение правил - как тесты для кода, только для политики.
Смысл execpolicy - в детерминированности там, где суждение модели ненадёжно. Модель может по-разному отнестись к одной и той же команде в разных формулировках; правило по префиксу решает одинаково всегда. Поэтому опасные или требующие внимания семейства команд - внешние мутации, доступ к чувствительному - выносят в execpolicy, а не полагаются на то, что агент "сам поймёт". Детерминированная политика на префиксах - это граница, которую можно прочитать, протестировать и объяснить в ревью.
Типичные провалы вокруг execpolicy предсказуемы. Написать правило без match и not_match и промахнуться мимо намерения. Понадеяться, что широкий allow перекроет забытый forbidden, - хотя строгое решение побеждает. Забыть, что формат экспериментальный, и перенести устаревший синтаксис по памяти. И не прогнать execpolicy check перед применением. Описывайте правила по префиксу с тестами, помните порядок строгости, сверяйте синтаксис с доками и проверяйте каждое изменение check'ом.
# Starlark, файл rules/ рядом с активным config layer
prefix_rule(
pattern = ["gh", "pr", ["view", "list"]],
decision = "prompt", # forbidden > prompt > allow
justification = "External GitHub read requires an explicit check",
match = ["gh pr view 7888", "gh pr list --limit 10"],
not_match = ["gh issue list"],
)# Негативный тест правила до применения
codex execpolicy check --pretty \
--rules ~/.codex/rules/default.rules \
-- gh pr view 7888 --json title,body,comments