Biconnector 26.1050.0: локальный BI Конструктор без расширения лицензии блокируется
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
В коробку biconnector 26.1050.0 вышел 8 сентября 2026 года (дата из официального канала «Битрикс24 changelog»), и весь релиз про лицензирование локального режима BI Конструктора, в котором Superset работает на вашем сервере. Этому режиму теперь нужны расширение лицензии со сроком, регион ru и редакция Энтерпрайз или Холдинг, а портал с уже подключённым сервером без срока расширения блокируется. Разработчикам придётся заменить удалённый SupersetHostMode::handleSwitchMode() и дописать четыре метода в свои реализации IntegratorInterface.
Проверьте, не заблокирован ли ваш BI Конструктор
Решение о блокировке принимает новый SelfHostedAvailability. Он проходит цепочку проверок по данным портала и в сеть не ходит. Его isBlocked() пропускает только два состояния, Available и ExtensionGrace, а четыре остальных закрывают работу:
TariffUnavailable, редакция не подходит;BoxLicenseExpired, истекла лицензия самой коробки.BoxLicenseTermберётLicense::getExpireDate(), доводит до конца дня и прибавляет 15 дней;ExtensionMissing, срока расширения нет, опция~bi_constructor_expired_atмодуля main пуста;ExtensionExpired, прошли срок расширения и 15 дней грейса после него.
Блокировка срабатывает в трёх местах. Новый middleware SelfHostedLicenseRestriction стоит первым в цепочке запросов SelfHostedIntegrator и на все управляющие запросы к серверу отвечает STATUS_INNER_ERROR, пропуская только четыре лицензионных. Эндпоинт bi_builder.php, который отдаёт строки данных, отвечает {"error":"LICENSE_EXPIRED"} и раз в сутки пишет предупреждение в лог. SupersetInitializer::startupSuperset() не запускает инстанс, если расширение не в состоянии Active.
Замок на дашборде, редиректы и ошибки в формах появляются в TariffUnavailable, ExtensionExpired и BoxLicenseExpired. Компоненты настроек, создания и редактирования дашбордов показывают ошибку с текстом состояния, прямая ссылка на дашборд ведёт обратно в список, публичная ссылка отдаёт STATUS = ERROR, а слайдеры импорта датасетов и внешних подключений редиректят на /. В ExtensionMissing пользователь видит только баннер, хотя bi_builder.php и управляющие запросы к серверу в этом состоянии отказывают так же, как в остальных.
Состояние портала можно прочитать из кода. Запустите это в своей консольной команде или на странице с подключённым прологом:
<?php declare(strict_types=1);
use Bitrix\BIConnector\Superset\Selfhost\License\SelfHostedAvailability;
use Bitrix\BIConnector\Superset\Selfhost\License\SelfHostedLicense;
use Bitrix\BIConnector\Superset\Selfhost\SupersetHostMode;
use Bitrix\Main\Loader;
Loader::requireModule('biconnector');
$availability = SelfHostedAvailability::getInstance();
$license = SelfHostedLicense::getInstance();
$status = [
'mode' => SupersetHostMode::getMode(), // 'cloud' или 'selfhosted'
'availability' => $availability->getState()->name, // в облачном режиме здесь Available
'blocked' => $availability->isBlocked(),
'boxExpired' => $availability->isBoxLicenseExpired(),
'extension' => $license->getState()->name, // None, Active, Grace, Expired
'extensionExpiry' => $license->getExpiryDate()?->format('Y-m-d'),
'extensionBlockDate' => $license->getBlockDate()?->format('Y-m-d'),
'switchErrors' => SupersetHostMode::checkSelfHostedRestrictions()->getErrorMessages(),
];
print_r($status);
getExpiryDate() вернёт null, если срока нет или значение опции отброшено. SelfHostedLicense принимает только целое число от 1767225600 (1 января 2026 UTC) до «сейчас плюс 10 лет», остальное выкидывает с предупреждением в логе. getBlockDate() прибавляет к сроку грейс, по умолчанию 15 дней. В switchErrors окажутся причины, по которым портал сейчас не пустят в локальный режим.
Кто записывает срок расширения, по коду не видно. В коде коробки на момент релиза ~bi_constructor_expired_at в bitrix/modules только читают, и только в biconnector, а комментарий в LicenseOption.php говорит, что её пишет лицензионный слой из полей обновления. Когда опция меняется, новый обработчик main:OnAfterSetOption_~bi_constructor_expired_at отправляет срок на сервер аналитики через SelfHostedLicenseSync.
О приближении срока будет напоминать агент SelfHostedLicenseNotifier::notifyExpiringLicense(). Раз в сутки он шлёт администраторам личные уведомления за 90 и за 30 дней до срока. Уведомление о восстановлении работы отправляет обработчик записи опции, SelfHostedLicenseSync::onExpiryDateOptionSet(), фоновой задачей и только если работа до этого останавливалась. В списке дашбордов администратор за 90 дней до срока увидит попап, а списки дашбордов и статистики использования выводят баннер.
Переход в локальный режим теперь с условиями
В 26.1000.0 handleSwitchMode() проверял только, что значение режима допустимое и портал не облачный. Теперь SupersetHostMode::checkSelfHostedRestrictions() проверяет по порядку:
- регион лицензии входит в
ALLOWED_REGIONS = ['ru']; - в
License::getCodes()естьEnterpriseилиHolding, причём пустой реестр кодов считается разрешением; - расширение в состоянии
ActiveилиGrace.
Отказы приходят с текстами BIC_SELFHOST_ERROR_REGION, BIC_SELFHOST_ERROR_EDITION и BIC_SELFHOST_ERROR_EXTENSION.
На странице настроек модуля вкладку локального режима в коробке видно, только если регион ru или портал уже в локальном режиме. Для перехода нужны адрес и пароль сервера, подключение должно пройти до переключения, а сразу после него страница вызывает SupersetInitializer::startupSuperset().
С грейсом вышла нестыковка. checkSelfHostedRestrictions() и SelfHostedAvailability в состоянии Grace разрешают и работу, и переключение, а startupSuperset() запускает инстанс только в Active. Портал, который переключили в локальный режим во время грейса, останется без запущенного инстанса.
Порталу вне ru, который уже работает в локальном режиме, вкладка видна, но сохранить настройки сервера он не сможет. SelfHostedConnectionService::updateConnectionSettings() первым делом проверяет регион и отвечает ошибкой, а страница показывает общее BIC_SUPERSET_CONNECTION_ERROR, по которому причину не понять.
Не выключайте локальный сервер до возврата в облако
Обратное переключение теперь зависит от локального сервера. applySwitchMode(MODE_CLOUD) сначала просит инстанс сбросить лицензионное состояние, для этого SelfHostedLicenseSync::resetInstanceLicenseState() вызывает на нём resetSelfHostedLicense(). Если сервер недоступен, отказал, бросил исключение, вообще не подключён или в этот момент идёт доставка срока, переключение отклоняется с BIC_SELFHOST_ERROR_LICENSE_RESET. Режим остаётся локальным, данные не удаляются.
Сначала переключите портал в облако и только потом гасите сервер. Блокировка этому не мешает, потому что лицензионные запросы, сброс в их числе, идут в обход middleware блокировки. Запись о сервере удаляет новый SelfHostedConnectionService::disconnectServer(), и если он не справится, переключение всё равно завершится.
Замените handleSwitchMode() парой методов
SupersetHostMode::handleSwitchMode(string $newMode): Result удалён, и вызов из вашего кода закончится фаталом Call to undefined method. Вместо него два метода. checkSwitchMode(string $newMode): Result только проверяет и ничего не меняет. applySwitchMode(string $newMode): Result переключает режим и удаляет данные того, из которого портал уходит. В докблоке сказано, что это не проверка и метод неидемпотентный. Режим, который уже включён, checkSwitchMode() пропускает без ошибок, а applySwitchMode() на нём не отказывает и снова стирает данные Superset, поэтому сравнивайте режим с getMode() до вызова, как это делает options.php.
Между проверкой и применением options.php вставляет свой шаг и подключает сервер через SelfHostedConnectionService::updateConnectionSettings(). Если переводите портал в локальный режим из своего кода, повторите ту же последовательность. Возврат в облако обходится без промежуточного шага:
<?php declare(strict_types=1);
namespace Vendor\Bi\Application;
use Bitrix\BIConnector\Superset\Selfhost\SupersetHostMode;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;
final class SupersetModeSwitcher
{
public function switchToCloud(): Result
{
Loader::requireModule('biconnector');
if (SupersetHostMode::getMode() === SupersetHostMode::MODE_CLOUD) {
return new Result(); // на текущем режиме applySwitchMode() не отказывает, а заново стирает данные Superset
}
$check = SupersetHostMode::checkSwitchMode(SupersetHostMode::MODE_CLOUD);
if (!$check->isSuccess()) {
return $check;
}
// если локальный сервер не ответит, вернётся ошибка с текстом BIC_SELFHOST_ERROR_LICENSE_RESET
return SupersetHostMode::applySwitchMode(SupersetHostMode::MODE_CLOUD);
}
}
Проверьте свои реализации и наследников
В IntegratorInterface четыре новых метода, все возвращают IntegratorResponse:
setSelfHostedLicenseExpiration(?DateTime $date);setBoxLicenseExpiration(?DateTime $date);setSelfHostedEditionVerdict(bool $isAllowed);resetSelfHostedLicense().
Своя реализация интерфейса без них перестанет загружаться, например та, которую подменяют через IntegratorFactory::setInstance(). В ядре их реализуют два класса. ProxyIntegrator отказывает локально, не отправляя запрос в прокси, со статусом STATUS_INNER_ERROR и кодами selfhosted_license_not_applicable, box_license_not_applicable, selfhosted_edition_verdict_not_applicable и selfhosted_license_reset_not_applicable. SelfHostedIntegrator передаёт их через команды Bitrix\Superset\Public\Commands\License\* модуля superset (они есть в superset 26.400.0), а те отправляют POST на /api/v1/bitrix/license/date/, box-date/, edition/ и reset/ инстанса, с таймаутами 5/15 с.
У BaseMenuItemCreator protected-метод createDashboardOpenEventFromMenu() получил третий параметр bool $isMarketAvailable = true, проверка маркета и тарифный слайдер переехали внутрь метода. Наследник, который его переопределяет, без нового параметра упадёт с фаталом при объявлении класса. IS_LOCKED у пунктов меню теперь считает новый protected isDashboardLocked(bool $isMarketAvailable = true), и пункт «Маркет» из getAdditionalItems() тоже получил IS_LOCKED.
Если вызываете SelfHostedConnectionService::updateConnectionSettings() сами, вне ru он теперь сразу возвращает ошибку. Одна транзакция внутри стала двумя, и адрес с токеном коммитятся до доставки срока лицензии. Когда генерация JWT-ключей или актуализация подключения срывается, новый приватный restorePreviousServer() возвращает прежние адрес и пароль.
SupersetInitializer::clearSupersetData() раньше удалял опции типизации датасетов, а теперь вызывает DatasetSettings::enableTypingForNewInstance(), и обе опции, TYPING_OPTION_NAME и TYPING_LOCK_OPTION_NAME, получают Y.
Выключатель проверки и длина грейса
Вручную настраиваются три опции модуля biconnector. selfhost_license_check_disabled = Y заставляет SelfHostedLicense::getState() всегда отвечать Active и один раз за запрос пишет об этом в лог. Проверки редакции и срока коробки он не отключает, так что TariffUnavailable и BoxLicenseExpired никуда не денутся. selfhost_license_grace_days и selfhost_box_license_grace_days задают длину грейса, по умолчанию 15 дней.
Комментарий в BoxLicenseTerm.php называет selfhost_box_license_grace_days инструментом для стенда и поддержки, а про selfhost_license_grace_days в SelfHostedLicense.php сказано «для стенда и тестов». По комментарию в SelfHostedLicense.php, инстанс считает свои 15 дней сам (SelfHostedLicenseService.GRACE_SECONDS на его стороне), поэтому константу SelfHostedLicense::GRACE_DAYS надо менять на обеих сторонах вместе. Опцию selfhost_license_grace_days инстанс не получает, и сокращённый ею грейс остановит портал раньше инстанса. selfhost_box_license_grace_days уже входит в срок коробки, который уходит на инстанс. Выключатель действует только на портале. Срок для инстанса SelfHostedLicenseSync берёт из getExpiryDate(), мимо getState(), и отправляет как есть.
БД, опции и агенты
Схема БД не менялась. В install/migrations/events.php добавлен обработчик main / OnAfterSetOption_~bi_constructor_expired_at → SelfHostedLicenseSync::onExpiryDateOptionSet. В b_option biconnector пишет служебные опции с префиксом ~selfhost_: отметки доставленных сроков и вердикта редакции, время и паузу следующего повтора, флаги предупреждений и ~selfhost_license_work_stopped.
Агенты SelfHostedLicenseSync::retryAgent(); (интервал 300 с, пауза между повторами удваивается до 3600 с) и SelfHostedLicenseNotifier::notifyExpiringLicense(); (раз в сутки) в install/migrations/agents.php не попали. Их ставит applySwitchMode() при переходе в локальный режим и снимает при выходе. Порталам, которые уже в локальном режиме, их по комментарию в SupersetHostMode.php ставит обновление. Скрипта обновления нам не видно, и проверить это мы не смогли.
Мелочи и находки
SelfHostedLicenseSync::deliverPendingEditionVerdict()читаетpreviousVerdictдля лога уже послеOption::set(), поэтому в поле «предыдущий вердикт» попадает новое значение.- Там же бесхозный докблок про откладывание повторов висит прямо над докблоком
readExpectedEditionVerdict(), а комментарий кisDeliveryPending()говорит о двух сроках, хотя доставляются три значения, вердикт и два срока. - Про
EXTENDED_EDITION_CODES = ['Enterprise', 'Holding']комментарий сам признаёт, что второй код избыточен. Реестр кумулятивный, и стенд с топовой редакцией несётPortal,Communications,Enterprise,Holding. - В
LicenseOption.phpпредупреждают, что имя опции срока менять нельзя, иначе все коробки молча потеряют лицензию. - Из
options.phpушёл'' // TODO Add en article. Ссылку на инструкцию по развёртыванию теперь отдаётLicenseLinks::getDeployGuideUrl(), вне СНГ онаnull, и строка не выводится. Ссылку экранируетhtmlspecialcharsbx(), раньше она подставлялась в текст как есть. - Ветка сохранения адреса и пароля сервера проверяет режим через
SupersetHostMode::getMode(), а не по$_REQUEST['superset_mode'].group_rights2.phpв ветке сохранения страница подключает только при успехе. Integration\Superset\Agent::notifyAllAdmins()был private, стал public.IntegratorFactory::getSelfHostedInstance()отдаёт локальный интегратор при любом текущем режиме.- В новой разметке страницы настроек, шаблонов и грида везде стоят
data-testid="biconnector-selfhost-…", ARIA-роли иaria-describedby.
Что делать
- Если BI Конструктор у вас в локальном режиме, прочитайте состояние кодом из примера. При
ExtensionMissingзамков в интерфейсе нет, ноbi_builder.phpуже отвечаетLICENSE_EXPIRED. - Проверьте, что агенты
SelfHostedLicenseSync::retryAgent();иSelfHostedLicenseNotifier::notifyExpiringLicense();есть в расписании. - Переходите в локальный режим, когда
checkSelfHostedRestrictions()без ошибок, а срок расширения вActive. В грейсе инстанс после переключения не запустится. - Возвращаясь в облако, не выключайте локальный сервер, пока переключение не пройдёт.
- Поищите в коде
handleSwitchMode(, свои реализацииIntegratorInterfaceи наследниковBaseMenuItemCreator, которые переопределяютcreateDashboardOpenEventFromMenu().
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\BIConnector\Superset\Selfhost\SupersetHostMode::handleSwitchMode()
|
Удалено |
Bitrix\BIConnector\Superset\Selfhost\SupersetHostMode::checkSwitchMode() + Bitrix\BIConnector\Superset\Selfhost\SupersetHostMode::applySwitchMode()
|