feat(db-cfe-admin): навык администрирования расширений в базе

Расширение можно было положить в базу, но нельзя было посмотреть, что там лежит,
в каком оно состоянии, применяется ли, и убрать лишнее — всё это делалось руками
через 1cv8 и ibcmd.

Четыре команды: list (состав и свойства подключения), check (применимость и
синтаксический контроль), set-properties (безопасный режим, активность, защита от
опасных действий, область действия, профиль, РИБ), delete.

Конструкция опирается на замеры платформы (8.3.24.1691 и 8.3.27.1859):

- /CheckModules не нужен: /CheckConfig с теми же контекстными флагами даёт
  дословно тот же вывод и код возврата, но умеет вдобавок конфигурационные
  проверки. Одна команда платформы вместо двух, при запросе modules+config —
  один запуск;
- обе проверки БЕЗ флагов контекста рапортуют «ошибок не обнаружено» с кодом 0
  на заведомо сломанном модуле, поэтому набор контекстов всегда явный;
- применимость и синтаксис друг друга не заменяют (первая слепа к синтаксису,
  второй — к дрейфу контроля), отсюда умолчание apply,modules;
- коды возврата разные: применимость 1, /CheckConfig 101;
- /DeleteCfg -Extension "" возвращает 0, рапортует успех и удаляет ПЕРВОЕ
  расширение из списка, поэтому пустое имя отбивается до вызова платформы, а
  отсутствие имени никогда не значит «все»;
- список и удаление делает Конфигуратор (работает всегда и на серверной базе),
  свойства — ibcmd, которого в установке платформы может не быть: тогда колонки
  помечены прочерком с названной причиной, а set-properties отказывает внятно.

Значения флагов словесные (on/off), а не +/-: значение "-" через powershell.exe
-File парсер съедает молча — проверено, у соседнего db-update -Dynamic "-" по
этой причине не работает вовсе.

delete и set-properties проверяют результат перечитыванием состояния, а не
кодом возврата платформы.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
This commit is contained in:
Nick Shirokov
2026-09-04 19:01:22 +03:00
co-authored by Claude Opus 5
parent d4832ce363
commit a0f2c4988a
32 changed files with 2733 additions and 14 deletions
@@ -0,0 +1,5 @@
{
"script": "db-cfe-admin/scripts/db-cfe-admin",
"setup": "none",
"args": []
}
@@ -0,0 +1,29 @@
{
"name": "check: платформа отвергла расширение (код 1) — дрейф виден в отчёте, exit 1",
"fakePlatform": {
"log": "Проба: Текст метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Name",
"Проба",
"-Checks",
"apply"
],
"expectError": true,
"expect": {
"stdoutContains": [
"применимость",
"ОШИБКА",
"не соответствует методу",
"Итог: провалено 1 из 1"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор вывода платформы"
}
@@ -0,0 +1,26 @@
{
"name": "check: обе проверки прошли — контексты названы явно, exit 0",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Name",
"Проба"
],
"expect": {
"stdoutContains": [
"применимость",
"модули",
"(ThinClient, Server)",
"Итог: пройдено 2 из 2"
],
"stdoutNotContains": "ОШИБКА"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется сборка отчёта"
}
@@ -0,0 +1,28 @@
{
"name": "check: синтаксическая ошибка модуля (код 101) считается провалом",
"fakePlatform": {
"log": "{Проба ОбщийМодуль.БатчМод.Модуль(13,23)}: Ожидается символ ')'\r\n",
"exit": 101
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Name",
"Проба",
"-Checks",
"modules"
],
"expectError": true,
"expect": {
"stdoutContains": [
"модули",
"ОШИБКА",
"Ожидается символ"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор кода возврата платформы"
}
@@ -0,0 +1,27 @@
{
"name": "check без -Name: сообщения платформы печатаются построчно и называют своё расширение",
"fakePlatform": {
"log": "Проба: Текст метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\nВторая: Список параметров метода \"Вторая_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Checks",
"apply"
],
"expectError": true,
"expect": {
"stdoutContains": [
" применимость ОШИБКА",
" Проба: Текст метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".",
" Вторая: Список параметров метода \"Вторая_Расчёт\" не соответствует методу \"Расчёт\"."
],
"stdoutNotContains": " | "
},
"noSnapshot": "проверяется форма отчёта — навык ничего не пишет в рабочий каталог"
}
@@ -0,0 +1,21 @@
{
"name": "Состав команды: без -Name синтаксическая проверка идёт по всем расширениям",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Checks",
"modules"
],
"expect": {
"stdoutContains": "/CheckConfig -ThinClient -Server -AllExtensions",
"stdoutNotContains": "-Extension"
},
"noSnapshot": "проверяется командная строка, переданная платформе"
}
@@ -0,0 +1,26 @@
{
"name": "Состав команды: -Checks config добавляет конфигурационные флаги и не тянет контексты",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Name",
"Проба",
"-Checks",
"config"
],
"expect": {
"stdoutContains": "/CheckConfig -ConfigLogIntegrity -IncorrectReferences -UnreferenceProcedures -HandlersExistence -EmptyHandlers -Extension \"Проба\"",
"stdoutNotContains": [
"-ThinClient",
"-Server"
]
},
"noSnapshot": "проверяется командная строка, переданная платформе"
}
@@ -0,0 +1,27 @@
{
"name": "Состав команды: по умолчанию проверяются применимость и модули, контексты заданы явно",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Name",
"Проба"
],
"expect": {
"stdoutContains": [
"/CheckCanApplyConfigurationExtensions -Extension \"Проба\"",
"/CheckConfig -ThinClient -Server -Extension \"Проба\""
],
"stdoutNotContains": [
"-ConfigLogIntegrity",
"-AllExtensions"
]
},
"noSnapshot": "проверяется командная строка, переданная платформе"
}
@@ -0,0 +1,19 @@
{
"name": "Состав команды: список расширений снимается /DumpDBCfgList",
"fakePlatform": {
"log": "Альфа\r\n"
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"list"
],
"expect": {
"stdoutContains": "DESIGNER /F \"",
"stdoutNotContains": "-Extension"
},
"noSnapshot": "проверяется командная строка, переданная платформе"
}
@@ -0,0 +1,24 @@
{
"name": "delete: платформа отчиталась успехом, но расширение осталось — навык это ловит",
"fakePlatform": {
"log": "Альфа\r\n"
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"delete",
"-Name",
"Альфа"
],
"expectError": true,
"expect": {
"stdoutContains": [
"/DeleteCfg -Extension \"Альфа\"",
"platform reported success, but 'Альфа' is still in the infobase"
]
},
"noSnapshot": "проверяется постусловие удаления"
}
@@ -0,0 +1,21 @@
{
"name": "delete: после удаления список перечитывается, и остаток попадает в итог",
"fakePlatform": {
"log": "Альфа\r\n"
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"delete",
"-Name",
"Бета"
],
"expectError": true,
"expect": {
"stdoutContains": "Error: extension 'Бета' not found in the infobase"
},
"noSnapshot": "негативный кейс — до записи дело не доходит"
}
@@ -0,0 +1,20 @@
{
"name": "-All применим только к delete",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"list",
"-All"
],
"expectError": true,
"expect": {
"stdoutContains": "-All applies to delete only"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,23 @@
{
"name": "-Context без синтаксической проверки — отказ, а не тихо проигнорированный параметр",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Checks",
"config",
"-Context",
"Server"
],
"expectError": true,
"expect": {
"stdoutContains": "-Context applies to the syntax check"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,19 @@
{
"name": "delete без -Name и без -All — отказ",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"delete"
],
"expectError": true,
"expect": {
"stdoutContains": "an omitted name never means all"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,21 @@
{
"name": "пустое -Name не значит «все»: платформа на пустом имени удаляет первое расширение",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"delete",
"-Name",
""
],
"expectError": true,
"expect": {
"stdoutContains": "Error: -Name is empty"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,22 @@
{
"name": "-Name и -All вместе — отказ",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"delete",
"-Name",
"Альфа",
"-All"
],
"expectError": true,
"expect": {
"stdoutContains": "mutually exclusive"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,17 @@
{
"name": "команда не указана — отказ",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib"
],
"expectError": true,
"expect": {
"stdoutContains": "specify a command"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,26 @@
{
"name": "set-properties без ibcmd — внятный отказ, а не тихая деградация",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"set-properties",
"-Name",
"Альфа",
"-SafeMode",
"off"
],
"expectError": true,
"expect": {
"stdoutContains": [
"cannot set properties",
"ibcmd"
]
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,21 @@
{
"name": "set-properties без единого свойства — отказ, а не молчаливый no-op",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"set-properties",
"-Name",
"Альфа"
],
"expectError": true,
"expect": {
"stdoutContains": "needs at least one property"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,21 @@
{
"name": "неизвестная проверка — отказ со списком допустимых",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Checks",
"apply,wat"
],
"expectError": true,
"expect": {
"stdoutContains": "unknown check"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,19 @@
{
"name": "неизвестная команда — отказ со списком допустимых",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"wat"
],
"expectError": true,
"expect": {
"stdoutContains": "unknown command"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,21 @@
{
"name": "неизвестный контекст — отказ со списком допустимых",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Context",
"Мобильный"
],
"expectError": true,
"expect": {
"stdoutContains": "unknown context"
},
"noSnapshot": "негативный кейс — навык отказывает до вызова платформы"
}
@@ -0,0 +1,39 @@
{
"name": "list: свойства подхватываются из ibcmd, флаги печатаются по-русски",
"fakePlatform": {
"log": "Альфа\r\n"
},
"preRun": [
{
"writeFile": {
"path": "ibcmd.cmd",
"content": "@echo off\r\necho name : \"Альфа\"\r\necho version : \r\necho active : yes\r\necho purpose : customization\r\necho safe-mode : yes\r\necho unsafe-action-protection : no\r\necho used-in-distributed-infobase : no\r\necho scope : infobase\r\nexit /b 0\r\n",
"executable": true
}
},
{
"writeFile": {
"path": "ibcmd.sh",
"content": "#!/bin/sh\ncat <<'EOF'\nname : \"Альфа\"\nversion : \nactive : yes\npurpose : customization\nsafe-mode : yes\nunsafe-action-protection : no\nused-in-distributed-infobase : no\nscope : infobase\nEOF\nexit 0\n",
"executable": true
}
}
],
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"list"
],
"expect": {
"stdoutContains": [
"Альфа",
"Адаптация",
"Информационная база"
],
"stdoutNotContains": "свойства недоступны"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор вывода ibcmd"
}
@@ -0,0 +1,25 @@
{
"name": "list: без ibcmd имена читаются Конфигуратором, колонки свойств помечены прочерком",
"fakePlatform": {
"log": "Альфа\r\nБета\r\n"
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"list"
],
"expect": {
"stdoutContains": [
"[РАСШИРЕНИЯ]",
"(2)",
"Альфа",
"Бета",
"—",
"свойства недоступны"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор вывода платформы"
}
@@ -0,0 +1,28 @@
{
"name": "База из реестра: путь разрешения реквизитов хранилища проходится обоими портами",
"fakePlatform": {
"log": "Альфа\r\n"
},
"cwd": "workDir",
"preRun": [
{
"writeFile": {
"path": ".v8-project.json",
"content": "{\n \"databases\": [\n {\n \"id\": \"probe\",\n \"type\": \"file\",\n \"path\": \"ib\"\n }\n ]\n}"
}
}
],
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"list"
],
"expect": {
"stdoutContains": "Альфа",
"stdoutNotContains": "Traceback"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется путь с реестром баз"
}
@@ -0,0 +1,27 @@
{
"name": "Пароль в печатаемой команде замаскирован",
"fakePlatform": {
"log": ""
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Command",
"check",
"-Name",
"Проба",
"-Checks",
"apply",
"-UserName",
"Админ",
"-Password",
"СуперСекрет"
],
"expect": {
"stdoutContains": "/P***",
"stdoutNotContains": "СуперСекрет"
},
"noSnapshot": "проверяется маскировка в печатаемой команде"
}
+13 -13
View File
@@ -71,7 +71,7 @@ const FAMILIES = [
// вверх. Группа db-* использует её же, чтобы найти запись базы и взять реквизиты
// хранилища — задача одна, поэтому семья общая, а не вторая с тем же телом.
{ id: 'full', authority: 'cf-edit',
consumers: ['cfe-borrow', 'db-dump-xml', 'db-load-git', 'db-load-xml', 'db-repo', 'db-update',
consumers: ['cfe-borrow', 'db-cfe-admin', 'db-dump-xml', 'db-load-git', 'db-load-xml', 'db-repo', 'db-update',
'form-add', 'form-compile', 'form-edit', 'help-add', 'interface-edit', 'meta-compile',
'meta-edit', 'meta-remove', 'mxl-compile', 'role-compile', 'skd-compile', 'skd-edit',
'subsystem-compile', 'subsystem-edit', 'template-add', 'xdto-compile', 'xdto-edit'] },
@@ -255,7 +255,7 @@ const FAMILIES = [
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump'] },
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'db-cfe-admin'] },
{ id: 'v8-only', authority: 'db-repo', consumers: [],
why: 'хранилище конфигурации ibcmd не поддерживает вовсе — нет такого режима, поэтому ветки ibcmd нет; вместо неё проверка усечённых ключей /ConfigurationRepository*, которые платформа не считает ошибкой, а запускает конфигуратор интерактивно' },
],
@@ -266,7 +266,7 @@ const FAMILIES = [
// db-run запускает Предприятие и не ждёт процесс — общей обвязки запуска не использует.
{ id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-repo', 'db-update', 'epf-build', 'epf-dump'] },
'db-load-xml', 'db-repo', 'db-update', 'epf-build', 'epf-dump', 'db-cfe-admin'] },
],
},
{
@@ -274,7 +274,7 @@ const FAMILIES = [
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-repo', 'db-update', 'epf-build', 'epf-dump'] },
'db-load-xml', 'db-repo', 'db-update', 'epf-build', 'epf-dump', 'db-cfe-admin'] },
],
},
{
@@ -282,7 +282,7 @@ const FAMILIES = [
variants: [
{ id: 'base', authority: 'db-dump-cf',
consumers: ['db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump'] },
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'db-cfe-admin'] },
],
},
{
@@ -290,7 +290,7 @@ const FAMILIES = [
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] },
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish', 'db-cfe-admin'] },
],
},
{
@@ -298,7 +298,7 @@ const FAMILIES = [
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] },
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish', 'db-cfe-admin'] },
],
},
{
@@ -306,7 +306,7 @@ const FAMILIES = [
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] },
'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish', 'db-cfe-admin'] },
],
},
@@ -321,19 +321,19 @@ const FAMILIES = [
// конфигурацией базы.
{
name: 'repository: same_path', py: 'same_path', ps1: 'Test-SamePath',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update', 'db-cfe-admin'] }],
},
{
name: 'repository: find_project_database', py: 'find_project_database', ps1: 'Find-ProjectDatabase',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update', 'db-cfe-admin'] }],
},
{
name: 'repository: resolve_settings', py: 'resolve_repository_settings', ps1: 'Resolve-RepositorySettings',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update', 'db-cfe-admin'] }],
},
{
name: 'repository: args', py: 'repository_args', ps1: 'Get-RepositoryArgs',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update', 'db-cfe-admin'] }],
},
// ─── Значения свойств-перечислений ───────────────────────────────────────
@@ -371,7 +371,7 @@ const FAMILIES = [
{ id: 'base', authority: 'meta-compile',
consumers: [
'cf-edit', 'cf-info', 'cf-init', 'cf-validate', 'cfe-borrow', 'cfe-diff', 'cfe-init',
'cfe-patch-method', 'cfe-validate', 'db-create', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml',
'cfe-patch-method', 'cfe-validate', 'db-cfe-admin', 'db-create', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml',
'db-load-cf', 'db-load-dt', 'db-load-git', 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build',
'epf-dump', 'epf-init', 'epf-validate', 'erf-init', 'form-add', 'form-compile',
'form-decompile', 'form-edit', 'form-info', 'form-remove', 'form-validate', 'help-add',