Нажали Approve на одно действие, а workflow попытался выполнить другое: разбор n8n + MCP + Bitrix24
Положительный флаг `approved=true` сам по себе не доказывает, что система выполнила именно то действие, которое подтвердил человек. В связке n8n, Groq, MCP и Bitrix24 это особенно важно: интерфейс согласования и узел, выполняющий запись, могут получать параметры из разных веток. В результате пользователь одобряет действие A, а downstream-узел формирует запрос на действие B.
В моём лабораторном сценарии речь шла об изменении заголовка синтетической задачи в Bitrix24. Человек видел одни параметры операции и подтверждал их. Затем отдельный MCP client node самостоятельно собирал вызов `update_task`, используя собственную конфигурацию. Между этими двумя этапами не существовало жёсткого технического правила, которое гарантировало бы равенство одобренных и фактически отправленных параметров.
Упрощённая схема выглядела так:
1. Workflow формирует предложение об изменении задачи.
2. Пользователь видит его и нажимает Approve.
3. В системе появляется `approved=true`.
4. Независимый узел готовит вызов MCP.
5. MCP вызывает `update_task` с параметрами, которые не обязаны совпадать с показанными пользователю.
Один из тестовых прогонов подтвердил наличие проблемы:
| Контрольная точка | Наблюдаемый результат |
|---|---|
| Одобренное человеком действие | Action A |
| Фактический вызов downstream-узла | Action B |
| Совпадение существенных параметров | Нет |
| Ответ Bitrix24 | Попытка отклонена |
| Изменение задачи | Не зафиксировано |
Здесь важно точно описывать результат. Эксперимент не показал, что неодобренное изменение успешно записалось в Bitrix24. Не было доказательств и того, что модель самостоятельно подменила параметры. Был установлен другой, более узкий факт: архитектура workflow позволяла сформировать попытку вызова, параметры которой отличались от одобренного действия.
Проблема находилась не обязательно в самой языковой модели. Уязвимость возникла на уровне передачи данных между узлами. Флаг согласования сообщал только: "какое-то действие разрешено". Он не содержал криптографически или логически связанного описания того, что именно разрешено выполнить.
Почему `approved=true` недостаточно
Булево значение не описывает объект согласования. Оно не отвечает на несколько критически важных вопросов:
- какую задачу разрешено менять;
- какое поле можно обновить;
- какое значение было одобрено;
- кто и когда подтвердил операцию;
- не изменились ли параметры после согласования;
- совпадает ли фактический вызов с отображённым пользователю предложением.
Если downstream-узел получает идентификатор задачи, новое значение заголовка и текст операции из другого источника, то `approved=true` превращается в общий переключатель. Такой переключатель разрешает продолжить выполнение, но не ограничивает конкретную операцию.
Для безопасной архитектуры нужно связывать согласование не с флагом, а с полноценным описанием действия.
Action Envelope как единый объект операции
Исправление началось с введения единого Action Envelope. В него вошли все существенные параметры:
- идентификатор задачи;
- тип операции;
- имя изменяемого поля;
- прежнее значение;
- новое значение;
- идентификатор запроса;
- момент формирования;
- субъект, подтвердивший действие;
- срок действия согласования.
Именно этот объект должен отображаться пользователю, проходить процедуру Approve и затем передаваться в узел записи без повторной сборки из независимых переменных.
Условно логика должна выглядеть следующим образом:
```text
Action Envelope
↓
Отображение пользователю
↓
Approve
↓
Проверка подписи и статуса
↓
Pre-read актуального состояния
↓
update_task с тем же Envelope
↓
Post-write verification
```
Ключевой инвариант можно сформулировать так:
```text
approved_action == invoked_action
```
Сравнивать необходимо не второстепенные поля, а material parameters - параметры, способные изменить смысл или последствия операции. Для задачи это как минимум `task_id`, имя поля и новое значение.
Свежая проверка состояния перед записью
Даже если пользователь одобрил правильный Action Envelope, состояние объекта могло измениться за время ожидания. Другой оператор, автоматизация или параллельный workflow могли обновить задачу.
Поэтому перед записью необходим свежий pre-read. Он должен подтвердить:
1. что задача всё ещё существует;
2. что текущий заголовок совпадает с ожидаемым исходным значением;
3. что пользователь имеет право на такую операцию;
4. что Action Envelope ещё не истёк;
5. что запрос не был выполнен ранее.
Если исходное состояние изменилось, выполнение лучше остановить и запросить новое согласование. Это защищает не только от ошибок агента, но и от обычных конкурентных изменений.
Особенно важна проверка ожидаемого старого значения. Если в Envelope записано, что задача должна начинаться с `Draft`, а перед записью там уже находится `In progress`, автоматическая подмена состояния недопустима: нужно считать согласование устаревшим.
Связанный вызов MCP
MCP-узел не должен самостоятельно "догадываться", какие параметры использовать. Его задача - передать уже согласованный объект в конкретный инструмент.
Нежелательная схема:
```text
Approve получает параметры из ветки A
update_task получает параметры из ветки B
```
Надёжная схема:
```text
Approve подтверждает Envelope
update_task принимает тот же Envelope
```
Если требуется преобразование данных, оно должно быть явным и проверяемым. Например, допустимо перевести внутреннее поле `new_title` в аргумент MCP `title`, но нельзя незаметно заменить значение, идентификатор задачи или тип операции.
Полезно также сохранять хеш Action Envelope перед согласованием и сравнивать его перед вызовом. Тогда система сможет доказать, что в исполнение отправлен тот же объект, который видел пользователь.
Контроль после записи
Ответ MCP или Bitrix24 о том, что запрос принят, ещё не является полным доказательством результата. После `update_task` нужен post-write verification.
Workflow должен заново прочитать задачу и проверить:
- сохранился ли нужный идентификатор;
- изменилось ли именно предусмотренное поле;
- установилось ли одобренное значение;
- не были ли затронуты другие важные поля;
- соответствует ли итоговое состояние Action Envelope.
Если запись не подтверждена, выполнение следует пометить как неопределённое, а не как успешное. Это особенно важно при сетевых сбоях: таймаут не означает автоматически ни успех, ни ошибку.
Как реконструировать прогон
Просмотра общего статуса workflow недостаточно. Для расследования нужно собирать трассу по каждой границе:
- входной Action Envelope;
- версия и хеш объекта перед согласованием;
- данные, показанные пользователю;
- событие Approve;
- параметры, поступившие в MCP-узел;
- фактический payload `update_task`;
- ответ Bitrix24;
- состояние задачи до записи;
- состояние после записи;
- идентификатор корреляции.
У каждого события должны быть временная метка и общий `correlation_id`. Тогда можно восстановить не только итоговый статус, но и последовательность действий.
Минимальный контракт контроля
Из эксперимента получился компактный набор обязательных правил:
1. Согласование относится к конкретному Action Envelope, а не к отдельному булевому флагу.
2. Существенные параметры нельзя повторно собирать из независимых веток.
3. Перед записью требуется свежий pre-read.
4. Вызов MCP должен использовать тот же набор параметров, который прошёл согласование.
5. После записи требуется повторное чтение объекта.
6. Все границы должны иметь наблюдаемое evidence.
7. Повторный запуск должен быть защищён от дублирования операции.
8. Просроченное или уже использованное согласование необходимо отклонять.
Полезна и идемпотентность: повторная доставка одного и того же запроса не должна приводить к неожиданным дополнительным изменениям. Для этого можно применять уникальный идентификатор операции и хранить журнал исполненных Envelope.
Что проверять в реальной агентной системе
При аудите workflow я бы начинал не с вопроса "насколько умна модель", а с анализа полномочий. Нужно определить, где именно возникает право на запись и какой компонент способен сформировать финальный payload.
Затем стоит проверить:
- может ли пользователь увидеть все существенные параметры;
- можно ли изменить их после Approve;
- имеются ли независимые источники данных у узлов;
- что произойдёт при повторной доставке сообщения;
- как система реагирует на изменение объекта между Approve и write;
- существует ли доказуемая связь между разрешением и инструментальным вызовом;
- можно ли восстановить полный прогон по журналам.
Главный вывод прост: человеческое подтверждение должно быть не декларацией намерения, а разрешением на конкретный, неизменный и проверяемый объект операции. В противном случае интерфейс показывает одно действие, флаг разрешает "что-то выполнить", а фактический узел получает возможность сформировать другой запрос.
В данном сценарии Bitrix24 отклонил неподходящую попытку, поэтому состояние задачи осталось прежним. Но полагаться на защиту внешней системы нельзя. Надёжность должна обеспечиваться раньше - через единый Action Envelope, свежую проверку исходного состояния, связанный вызов MCP и обязательную проверку результата после записи.
