Bizproc 26.1100.0: агент удаляет процессы, зависшие на паузе, а импорт смарт-скриптов проверяет действия
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
В bizproc 26.1100.0 импорт смарт-скриптов через REST-конфигурацию начал проверять действия шаблона от имени импортирующего пользователя. До этого шаблон сохранялся без ValidateProperties, и через импорт в него могло попасть действие CBPCodeActivity, которое эта проверка разрешает только администратору. Всех, у кого работают бизнес-процессы, касается ещё и новый агент, который раз в сутки удаляет процессы, больше недели стоящие на паузе без шансов проснуться.
Решите, нужно ли вам автоудаление зависших процессов
Агент Bitrix\Bizproc\Infrastructure\Agent\ClearStuckPauseWorkflowAgent::run() ищет процессы, которые стоят в DelayActivity, RobotDelayActivity или WaitWorkDayActivity дольше clear_stuck_pause_grace_days дней и у которых нет живого механизма пробуждения. По умолчанию срок 7 дней, это StuckPauseWorkflowDetector::DEFAULT_GRACE_DAYS. Процесс, который ждёт своего таймера через месяц, агент не тронет, потому что его есть чем разбудить. Чтобы не удалить процесс, который как раз оживает, агент работает в два прохода. Первый отмечает кандидата, второй проверяет его ещё раз под блокировкой bizproc_<workflowId> и только потом удаляет. Удаление состоит из CBPDocument::killWorkflow($workflowId, false), SchedulerEventTable::deleteByWorkflow() и чистки resume-сообщений процесса.
На новой установке агент включается сразу, через install/migrations/agents.php с clear_stuck_pause_agent_active = 'Y'. На обновлении его зарегистрирует степпер ResumeMessageItemIdStepper, но только когда допишет ITEM_ID всем старым resume-сообщениям, и до этого момента автоудаление не работает. Выключить агента или поменять срок можно в настройках модуля. Там появились чекбокс «Автоматически удалять зависшие на паузе бизнес-процессы» и поле «Время ожидания...» (от 1 до 365 дней). Срок и выключатель хранятся в опциях clear_stuck_pause_grace_days и clear_stuck_pause_agent_active, и их можно выставить из кода:
<?php declare(strict_types=1);
use Bitrix\Main\Config\Option;
// ждать месяц вместо недели
Option::set('bizproc', 'clear_stuck_pause_grace_days', '30');
// или выключить автоудаление совсем
Option::set('bizproc', 'clear_stuck_pause_agent_active', 'N');
Агент, увидев 'N', удаляет сам себя, так что обратно его вернёт чекбокс (он вызывает ClearStuckPauseWorkflowAgent::register(), этот метод можно вызвать и из кода). Пока степпер не дописал ITEM_ID, чекбокс заблокирован, и выключить автоудаление заранее можно только этой опцией. Степпер значение 'N' не перезаписывает.
Каждое удаление агент пишет в именованный логгер bizproc.workflow.stuckPause. Чтобы потом было видно, какие процессы он удалил, опишите логгер в секции loggers и включите его в реестре логгеров, иначе LoggerFactory::createById() отдаст NullLogger:
<?php declare(strict_types=1);
// фрагмент bitrix/.settings_extra.php
return [
'loggers' => [
'value' => [
'bizproc.workflow.stuckPause' => [
'constructor' => static fn (): \Bitrix\Main\Diag\FileLogger => new \Bitrix\Main\Diag\FileLogger(
'/var/log/bitrix/bizproc-stuck-pause.log',
),
'level' => \Psr\Log\LogLevel::INFO,
],
],
'readonly' => true,
],
];
Включается логгер одной строкой:
<?php declare(strict_types=1);
(new \Bitrix\Main\Diag\LoggerRegistry())->enable('bizproc.workflow.stuckPause');
Допишите новые очереди в команду воркера
На порталах, где у bizproc включено scheduler_use_messenger_transport = Y, паузы больше не пишут продолжение в общую resume_workflow_queue. У каждой теперь своя очередь:
DelayActivityпишет вresume_workflow_delay_queue;RobotDelayActivityвresume_workflow_robot_delay_queue;WaitWorkDayActivityвresume_workflow_wait_workday_queue.
У всех трёх limit => 5 и total_processing_limit => 5. Старая resume_workflow_queue никуда не делась. В enum Bitrix\Bizproc\Activity\Enum\ResumeWorkflowQueue она значится как Legacy с пометкой @deprecated, рядом лежат новые Delay, RobotDelay и WaitWorkDay.
Воркер, который запускает messenger:consume со списком очередей, новые очереди сам не подхватит, и процессы после паузы будут стоять, пока их сообщения никто не разбирает. Добавьте очереди к тем, что уже перечислены в вашей команде:
cd /var/www/site && php bitrix/bitrix.php messenger:consume \
resume_workflow_queue \
resume_workflow_delay_queue \
resume_workflow_robot_delay_queue \
resume_workflow_wait_workday_queue \
--sleep=1 --time-limit=270 --no-interaction
resume_workflow_queue из списка не убирайте. Туда по-прежнему пишут подписки, созданные до обновления, и вызовы sendResumeWorkflowMessage() без явной очереди: параметр очереди у него по умолчанию равен ResumeWorkflowQueue::Legacy.
Ещё enqueueResumeWorkflow() теперь добавляет в сообщение ItemIdParam($workflowId). По этому ITEM_ID детектор ищет у процесса живое resume-сообщение, а агент потом удаляет сообщения удалённого процесса. Сообщение без ITEM_ID детектор не видит, и живой процесс выглядел бы брошенным, поэтому на обновлении агент ждёт степпер.
Верните массив из onDeadEndReached()
У абстрактного Bitrix\Bizproc\Public\Activity\Structure\FlowDirectedActivity::onDeadEndReached(CBPActivity $lastActivity) возвращаемый тип сменился с void на array. Ядро зовёт метод, когда у завершившегося дочернего действия нет выходов, и запускает действия по ссылкам, которые он вернёт (вида 'A1111_2222_3333_4444:i0'). Наследник с : void после обновления падает с фаталом. Штатные наследники уже переписаны: BaseComplexActivity возвращает [], а CBPNodeWorkflowActivity отдаёт следующее состояние через новый FlowStateRouterTrait. Если ваш узел наследует BaseComplexActivity и переопределяет этот метод, его тоже придётся поправить.
Так выглядит своё действие, которое из тупика уходит в запасную ветку:
<?php declare(strict_types=1);
// local/activities/custom/vendorrouteractivity/vendorrouteractivity.php
use Bitrix\Bizproc\Public\Activity\Structure\FlowDirectedActivity;
/**
* @property-read string $FallbackActivity
*/
final class CBPVendorRouterActivity extends FlowDirectedActivity
{
public function __construct($name)
{
parent::__construct($name);
$this->arProperties['FallbackActivity'] = '';
}
protected function getStartActivityNames(): array
{
$first = $this->arActivities[0] ?? null;
return $first !== null ? [$first->getName()] : [];
}
// до 26.1100.0 здесь был ': void', с ним класс теперь не загрузится
protected function onDeadEndReached(\CBPActivity $lastActivity): array
{
$fallback = (string)$this->FallbackActivity;
// пустой список: ветка кончилась, узел закроется, когда опустеет очередь
if ($fallback === '' || $lastActivity->getName() === $fallback) {
return [];
}
return [static::createInputName($fallback, 0)];
}
}
Если такое действие уезжает на разные порталы, закройте его от старых ядер ключом MIN_API_VERSION в описании. Ядро сверяет его с CBPRuntime::ACTIVITY_API_VERSION, а тот в этом релизе поднялся с 3 до 4. Набросок показывает только этот ключ и минимум полей, настройки узла для дизайнера в нём опущены:
<?php declare(strict_types=1);
// local/activities/custom/vendorrouteractivity/.description.php
use Bitrix\Main\Localization\Loc;
$arActivityDescription = [
'NAME' => Loc::getMessage('VENDOR_ROUTER_ACTIVITY_NAME'),
'TYPE' => 'activity',
'CLASS' => 'VendorRouterActivity',
'JSCLASS' => 'BizProcActivity',
'CATEGORY' => ['ID' => 'other'],
// при ACTIVITY_API_VERSION ниже 4 ядро исключит действие из списка
'FILTER' => ['MIN_API_VERSION' => 4],
];
Откройте getPropertiesMap() в наследниках CBPMailActivity
CBPMailActivity теперь реализует IBPConfigurableActivity, и его getPropertiesMap() стал public static вместо protected static. Наследник, который переопределяет метод как protected, не загрузится. В самой карте свойств у MailUserTo появился Setter, у MailCharset значение по умолчанию 'windows-1251', у DirrectMail значение 'Y'.
<?php declare(strict_types=1);
// local/activities/custom/vendormailactivity/vendormailactivity.php
\CBPRuntime::getRuntime()->includeActivityFile('MailActivity');
class CBPVendorMailActivity extends \CBPMailActivity
{
// с protected здесь теперь фатал: у родителя метод public
public static function getPropertiesMap(array $documentType, array $context = []): array
{
$map = parent::getPropertiesMap($documentType, $context);
// родитель теперь подставляет windows-1251
$map['MailCharset']['Default'] = 'UTF-8';
return $map;
}
}
Пройдитесь по остальным изменениям поведения
- Если
HandleFaultодного действия падает 3 раза подряд (MAX_FAULT_HANDLING_ATTEMPTS),CBPWorkflowзавершает процесс черезterminate(). Раньше очередь в такой ситуации могла крутиться бесконечно. Ошибку записи в журнал отслеживания при обработке сбоя новый приватныйwriteFaultTracking()отправляет в файловый лог, и второй раз процесс из-за неё не падает. Такую же правку получилCBPActivity. ActivityDescription::toArray()отдаётCOMPLEX_ACTIVITY_SETTINGSмассивом, а не объектомComplex\Settings.setComplexActivitySettings()принимаетComplex\Settings|array|null. Код, который вызывал методы у этого ключа, придётся перевести на массив.- Условия
inи!inв фильтрах действий (lib/activity/mixins/entityfilter.php) разбирают строку, которая начинается с[, как JSON-список значений. В списке может быть не больше 500 значений по 255 символов, весь JSON ограничен 65535 символами. Значение, которое не укладывается в лимиты, даёт заведомо пустой ORM-фильтр, и выборка сужается до нуля. - Из конвертеров шаблонов (
SequentialToNodeWorkflow,StateChildToNodeWorkflow,StateMachineToNodeWorkflow) удалены protected-методы раскладкиsetPosition(),movePosition*(),copyPosition(),getRow(),getColumn(),setChildPosition*(). УconvertChild()иconvertChildren()новая сигнатура сLayout\GridPoint,StateChildToNodeWorkflow::createTriggers()заменён наcreateTrigger(string $type), раскладку строят новые классыConverter\Canvas\*иConverter\Layout\*. Заденет это только тех, кто наследовалSequentialToNodeWorkflow. RagServiceберёт список допустимых расширений файлов базы знаний изBitrix\Rag\Public\Service\FileKnowledgeBasePublicService::getFileUploadConfig()->extensions. Опцииrag_tables_enabled,rag_pptx_enabledиrag_images_enabledмодуль больше не читает.
Обновитесь, если смарт-скрипты импортируют не администраторы
Импорт смарт-скрипта из REST-конфигурации идёт через Script\Manager::importScript() и importWorkflowTemplate() в lib/script/manager.php. Перед сохранением шаблона его действия теперь проверяет новый CBPWorkflowTemplateLoader::checkImportedTemplateActivities(array $template, CBPWorkflowTemplateUser $user), и проверка идёт от имени импортирующего. Скрипт с CBPCodeActivity теперь импортирует только администратор, потому что CBPCodeActivity::validateProperties() требует администратора. Если в вашем модуле есть свой импорт шаблонов, вызывайте этот метод до сохранения, ошибки проверки он вернёт в Main\Result.
Остальные правки безопасности:
- сортировка списка шаблонов проходит через белый список полей (
CBPWorkflowTemplateLoader::prepareTemplatesOrder()), и неизвестныеby/orderиз URL больше не вызываютArgumentException; upgradeActionдля AI-агентов доступен только по POST и только администратору;- у условий
in/!inпоявились лимиты и пустая выборка на невалидное значение, подробности выше; AiAgentBotAvatarиbuildTemplateFromSource()проверяют код на path traversal.
Проверьте, как AI-ассистент описывает ваше действие
Новый ActivityAiAutoDescriber строит AI-схему настроек действия из getPropertiesMap(), а если такого метода нет, из getPropertiesDialogMap(). Поля Title и EditorComment в схему не попадают. Для REST-действий схема строится по RestActivityTable.PROPERTIES в строгом режиме. Ручное описание в .ai.php (класс CBPAI<Code> с интерфейсом IBPActivityAiDescription) по-прежнему работает, и ActivityAiDescriptionMerger объединяет его с автосхемой. Читает результат BlockDescriptionService из bizprocdesigner.
Чтобы исключить действие из автоописания, поставьте в его .description.php ключ 'AI_AUTO_DESCRIPTION' => 'N'. В PHP за ним стоят ActivityDescription::setAiAutoDescription(string|bool) и isAiAutoDescriptionAllowed(). Схема из карты свойств тогда строиться не будет, а ручной .ai.php продолжит работать. В ActivityAiDescriptionResult появились поля source (ActivityAiDescriptionSource::Manual или Auto) и skippedSettings. AiDescription::getActivityDescription() кеширует результат, изолирует исключения отдельного действия и пишет их в логгер bizproc.aiassistant.activity_description.
Загляните в остальные новые API
- Трейты
Public\Activity\Structure\FlowListenerTrait(subscribeOutputFlow(),onFlowEvent()) иFlowStateRouterTrait(setNextState(),getNextStateName()).FlowListenerTraitподключён вCBPListenActivityиCBPStateNode,FlowStateRouterTraitвCBPNodeWorkflowActivity. - Действия
StateNode(узел состояния с портами «Вход», «Выход» и «Ожидание») иSetStateNodeвinstall/activities/bitrix/. ЧерезStateNodeконвертерStateMachineToNodeWorkflowпереводит БП со статусами в узловую схему. КомплексныйUnifiedNodeпоявляется, только если определена константаBitrix\Bizproc\Dev\ENV. - В
Public\Activity\ReturnDocumentTraitпоявились методыexecute(),buildDocumentPropertyMap()иvalidateDocumentProperty(). - Каталог возможностей узла: DTO
AvailableBlock,BlockAvailability,NodeActionCatalogEntryиNodeCapabilityCatalogвActivity\Dto\Complex,Settings::fromArray()с полемavailableBlocks, enumActionGroupиNodeBlockType, сервисbizproc.service.activity.capabilityCatalog. Вариативные параметры узла вродеframeColorNameуEmptyBlockActivityхранитActivity\Dto\NodeSettingsParam. - AI-агента можно обновить до новой версии системного шаблона экшеном
Infrastructure\Controller\Integration\AiAgent\Template::upgradeAction(int $templateId, ?array $constantValues = null)или действием гридаUpgradeAction. Версия копии хранится в настройкеORIGIN_SYSTEM_VERSION(WorkflowTemplateSettingsTable) и считается как sha256 от нормализованногоTEMPLATE, где ключи отсортированы, аCONSTANTSне учитываются. Public\Provider\WorkflowTemplate\AiAgentProvider::checkLaunchAccess(?int)возвращает стабильный код ошибкиAI_AGENT_START_ACCESS_DENIED. Сам bizproc этот метод не вызывает.Public\Service\Trigger\TriggerService::hasStartTrigger(string $triggerType, array $complexType)поверхInternal\Repository\Trigger\TriggerRepository.CBPWorkflowPersisterбросаетBitrix\Bizproc\Exception\EmptyWorkflowInstanceExceptionтам, где раньше былException("WorkflowNotFound")илиException("EmptyWorkflowInstance"). Класс наследует\Exception, так что старыеcatch (\Exception)продолжают ловить.Api\Enum\ErrorMessage::getCodedError()отдаёт ошибку с кодом, равным значению enum.NodesInstaller::getMessageReplacements()иprepareMessage()подставляют значения во фразы узлов при установке. Такbitrix_ai_day_plannerполучает#COPILOT_NAME#изCopilotNameService, по умолчанию'BitrixGPT'.NodesInstallerService::buildTemplateFromSource()собирает шаблон из файлов узла без записи в БД.- Команда
bizproc:ai-agent-generateпосле генерации запускает phpunit-тесты агента изbizproc/tests/nodes/AI_AGENT/<name>и при падении откатываетtemplate.jsonиinstaller.php. Опция--skip-testsтесты пропускает. - Для секции
loggersпоявились именованные логгерыbizproc.workflow.stuckPause,bizproc.aiassistant.activity_description,bizproc.runtime.activity_searcherиbizproc.imbot, и каждый включается черезLoggerRegistry::enable().
Дайте степперу дописать ITEM_ID
Схему таблиц bizproc описывает install/migrations/tables.php, изменения пришли туда и в install/migrations/agents.php:
- в
b_bp_workflow_instanceдва индекса для выборки кандидатов на очистку,ix_bp_wi_status_modified (STATUS, MODIFIED)иix_bp_wi_modified_id_owned_until (MODIFIED, ID, OWNED_UNTIL); - в
b_bp_messenger_workflow_resume_messageиндексIX_QUEUE_ID_ITEM_ID, аWorkflowResumeMessageTableполучилDeleteByFilterTrait; - агент
ClearStuckPauseWorkflowAgent::run();с интервалом 86400 секунд и опцииclear_stuck_pause_agent_active,clear_stuck_pause_grace_days,clear_stuck_pause_workflow_offset,clear_stuck_pause_sweep_fromиclear_stuck_pause_swept_until.
ResumeMessageItemIdStepper заполняет ITEM_ID у старых resume-сообщений порциями по 500 через updateMulti(..., ignoreEvents: true). События выключены намеренно, чтобы onBeforeUpdate не сдвигал UPDATED_AT. Если вы вешали свои обработчики на WorkflowResumeMessageTable, на этих обновлениях они не сработают.
Мелочи и находки
CBPGetUserActivityпри выборе руководителя с пропуском неработающих проверял$this->isUserWorking($skipTimeman), то есть булев флаг вместо ID. Теперь проверяется$headId. Заодно тип пользователя можно задать выражением (user_type_text), аuser_typeв диалоге выводится черезCUtil::JSEscape.- Хранилище или его поле нельзя удалить, пока
StorageItemDeleteStepperудаляет записи. Проверку делает новыйhasAgentsForStorage(), а пользователь видит сообщения..._DELETION_IN_PROGRESS. Сам степпер при ошибке выборки больше не падает, он завершается и будит процесс. WorkflowTemplateService::saveTemplate()отклоняет запись в несуществующий шаблон и черновик удалённого шаблона с кодомTEMPLATE_NOT_FOUND, а шаблон и настройки обновляет в одной транзакции. То же сделали вUpdateWorkflowTemplateCommandHandler.RestService::addActivityInternal()(через него идутbizproc.activity.addиbizproc.robot.add) ловилSqlQueryException, который в пространстве имёнBitrix\Bizprocне импортирован, поэтомуcatchничего не ловил. Теперь тамMain\DB\DuplicateEntryException. Похоже на исправлениеbizproc.robot.add, о котором пишет вендор.- Приватный
CBPDocument::unSignArray(?string)наnullи пустую строку возвращает[]. - Порты
StateNodeв.description.phpподписаны по-русски прямо в коде, без локализации:'Вход','Выход','Ожидание'. - В комментариях контроллера
AiAgent\Template(upgradeAction()иrelaunchAfterUpgrade()) остались ссылки на внутреннюю спецификацию: «ADR § 10.3», «API-01», «AC-017/AC-018», «ERR-002/003/005/006». Ещё одна, «P3.T2», есть вCBPSetupTemplateActivity. ВSequentialToNodeWorkflow::migrateTemplate()виситTODO active-migrationпро двойной старт при миграции на месте. CHTTPиз модуля ушёл целиком. Вadmin/task.php, компонентеbizproc.task.listи шаблонеbizproc.documentего заменилMain\Web\Uri.BaseReceiverприINSTANCE_LOCKEDоткладывает повтор на 60 секунд (INSTANCE_LOCKED_RETRY_DELAY), раньше задержки не было.- В шаблоне
bitrix_ai_day_plannerсообщения и бот адресуются руководителю ({=A7001_0001_0001_0001:MANAGER}), а у сообщения появилась кнопка с командойdiscussReport. Обработчик этой скрытой команды пришёл вBizprocBotи вызываетReportDiscussionService::discuss()из timeman. В «Управлении сайтом» модулейtimemanиimbotнет, и там этот путь не исполняется. VERSION_DATEвinstall/version.phpстарше, чем у предыдущей 26.1075.0.
Что делать
- Решите, нужны ли вам процессы, которые больше недели стоят на паузе. Если нужны, выключите
clear_stuck_pause_agent_activeили увеличьтеclear_stuck_pause_grace_days. Логгерbizproc.workflow.stuckPauseв любом случае опишите вloggersи включите черезLoggerRegistry::enable(). - При
scheduler_use_messenger_transport = Yдопишите три новые очереди в командуmessenger:consume, аresume_workflow_queueоставьте. - Найдите в своём коде
onDeadEndReachedи замените: voidна: array. - Найдите наследников
CBPMailActivityи сделайте ихgetPropertiesMap()публичным. - Обновитесь, если смарт-скрипты на портале импортируют пользователи без прав администратора.
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\Bizproc\Activity\Enum\ResumeWorkflowQueue::Legacy
|
Deprecated |
Bitrix\Bizproc\Activity\Enum\ResumeWorkflowQueue::Delay / Bitrix\Bizproc\Activity\Enum\ResumeWorkflowQueue::RobotDelay / Bitrix\Bizproc\Activity\Enum\ResumeWorkflowQueue::WaitWorkDay
|