ENFORCED vs ADVISORY: где гарантия реальна, а где инструкция
Что это
Самая частая ложь на этом рынке — выдать инструкцию за гарантию. «Движок НЕ допустит утечки секретов» звучит как enforcement, но если под капотом только текст в системном промпте — это надежда, а не барьер. Мы разделяем два уровня и помечаем каждый прямо в пакете:
- ENFORCED — нарушение блокирует код возврата. Есть процесс (хук или CI), который падает с ненулевым exit-кодом и физически не даёт действию/коммиту пройти. Это гарантия.
- ADVISORY — правило есть, оно строгое и буквальное, модель ему следует, но НЕТ процесса, который заблокирует нарушение. Это инструкция.
Почему честность здесь — не слабость, а фича
Полный DRM и полное принуждение на чужом рантайме невозможны: платящий читает свой plaintext, а часть таргетов исполняет целевая модель, которую мы не контролируем. Притворяться, что «всё enforced», — значит подставить пользователя на проде. Поэтому в INSTALL-заметке каждого таргета честно написан уровень принуждения по каждому классу правил.
Что реально ENFORCED сегодня
1. Guard-hook против катастрофы — Claude Code и Cursor. В базовый движок вшит guard-dangerous.sh как PreToolUse-хук: он deny-ит с exit 2 до запуска инструмента на rm -rf по корню/дому, DROP/TRUNCATE, git push --force, curl | bash. Это код возврата, а не текст — жёсткая гарантия на границе модели. Claude Code и Cursor поддерживают хуки на границе инструмента, поэтому на обоих этот слой ENFORCED; таргеты без такого рантайма (например, чистый AGENTS.md в редакторе без хуков) читают то же правило как ADVISORY-текст.
2. Переносимый предохранитель (Portable Safety Floor) — на ВСЕХ таргетах. Это уже собрано (exporters/safetyFloor.ts, подключено в экспорт через applySafetyFloor для каждого таргета). Пакет несёт:
.engine/hooks/pre-commit— самодостаточный POSIX-хук (чистыйsh+grep, без Node/husky): сканирует staged-файлы на секреты (GitHub/OpenAI/Slack/AWS-токены, приватные ключи) и разрушительные/BLOCKED‑DO‑NOT‑COMMIT-маркеры и падает с кодом 1 при совпадении;.github/workflows/engine-guard.yml— тот же скан на каждый push/PR, enforced независимо от того, вооружён ли локальный хук.
Скан-тело у хука и у CI одно и то же (SCAN_SH вербатим), поэтому «зелено локально» == «зелено в CI». Это конвертирует часть ADVISORY → ENFORCED уже и за пределами Claude.
Что честно ADVISORY
- Инварианты уровня логики (tenancy-фильтр в каждом запросе, i18n-синхрон, обратимость миграций) — движок требует их буквально, но их соблюдение проверяет вердикт призмы/модель, а не exit-код. Секрет и явно-разрушительную команду floor поймает; «забыл фильтр по org в новом запросе» — нет.
- Вердикты призм и вето — модель инструктирована подчиниться, но сам вердикт не даёт exit-кода: на таргете без соответствующего хука это не блокирующий рантайм.
- Verify-green / doneCriteria — ADVISORY, пока вы не завели их в свой pre-commit/CI. (Напомним: слой
doneCriteria10/10 во флагманский сид Quest пока не входит — это слой конструктора в разработке.)
Пример: вход → что делает движок → результат
Вход. Один и тот же движок ставят в два проекта: в таргет с хуками на границе инструмента (Claude Code / Cursor) и в редактор через чистый AGENTS.md без хуков. В обоих кто-то по ошибке коммитит файл, где в коде оказался литерал sk-abc123..., и в другом месте пробует выполнить rm -rf ~/.
Claude Code / Cursor (есть хуки на границе инструмента):
rm -rf ~/→PreToolUseguard-hook срабатывает ДО запуска:ЗАБЛОКИРОВАНО guard-hook: rm -rf по корню/домашней директории,exit 2. Команда не выполнилась. ENFORCED.- коммит с
sk-...→ переносимый pre-commit ловит секрет:ENGINE GUARD: possible secret in ...,exit 1. Коммит заблокирован. ENFORCED. - INSTALL-заметка честно пишет: «Claude Code / Cursor: PreToolUse-hook — ENFORCED на границе модели; этот git-hook + CI — дополнительный ENFORCED-слой».
Редактор с чистым AGENTS.md (без хуков):
- хука на границе инструмента нет — на этом таргете модель читает правило «не выполняй разрушительные команды» как ADVISORY-текст. Если модель послушалась — хорошо; гарантии на границе инструмента нет.
- НО: коммит с секретом всё равно ловится переносимым pre-commit (после
git config core.hooksPath .engine/hooks) и — что важнее — CIengine-guard.ymlроняет push/PR даже если локальный хук не вооружён. ENFORCED через CI. - INSTALL-заметка честно пишет: «Чистый AGENTS.md: git-hook (ENFORCED после вооружения) + CI (ENFORCED всегда). Текст правил, который читает модель, — ADVISORY».
Результат. Один класс защиты (секреты/катастрофа) — жёсткий на всех таргетах через floor+CI. Другой класс (логические инварианты) — честно помечен ADVISORY. Пользователь ЗНАЕТ, где стена, а где инструкция, — и не строит прод на иллюзии.
Дизайн-принцип
Смысл базового движка — сделать ADVISORY настолько буквальным и проверяемым, чтобы его исполняла даже слабая модель (поведение — рецепт, а не «догадайся»), И параллельно двигать максимум правил в ENFORCED-floor. Что жёстко — то помечено ENFORCED и доказуемо exit-кодом. Что инструкция — помечено ADVISORY. Никаких «100% защищено» там, где это неправда.