xml-structure-review · git:20260818.69d4582 · 2026-08-18 · sha256 638c9af4c052ee48
xml-structure-review git:20260818.69d4582A
Immutable. This exact content is served forever at /api/v1/blob/638c9af4c052ee48.
---
name: xml-structure-review
description: >-
Контур проверки XML-структур метаданных 1С: корректность файлов объектов, форм, схем
компоновки, ролей и макетов; регистрация объекта в составе конфигурации; сверка «диск ↔
состав» в обе стороны; права на объекты в ролях расширения. Вызывается оркестратором
quality-gate при изменении XML; напрямую — по запросу «проверь метаданные»,
«провалидируй расширение», «почему объект не попал в сборку».
license: MIT
---
# xml-structure-review — контур XML метаданных
Проверяет структуру выгрузки конфигурации и расширений. Применимость определяется не объёмом
правки, а фактом: **менялись ли XML метаданных**.
| Класс | Глубина |
|---|---|
| C0 | пропуск |
| C1 | не применим, если XML не менялся; иначе — валидация изменённых файлов |
| C2 | валидация изменённых объектов **плюс проверка регистрации** |
| C3 | полный проход: валидация, сверка диск↔состав в обе стороны, права в ролях |
## Прогон механики — субагент `xml-runner`
Запуск скриптов и разбор их вывода делегируй субагенту `xml-runner`, передав список изменённых
файлов и каталог выгрузки. Он вернёт вердикт, находки с путями и строками, а также раздел «не
проверено».
Причина в контексте: валидаторы печатают до тридцати ошибок на файл, и на правке класса C3 их
вывод вытесняет всё остальное раньше, чем дело дойдёт до отчёта. Твоя работа начинается после
его отчёта — триаж находок, привязка к типовым дефектам и решение, что чинить.
Субагента может не быть — тогда прогоняй скрипты сам по разделам ниже. Записи следа в обоих
случаях формируешь **ты**: субагент возвращает факты и в формат следа их не оформляет.
---
## 1. Сверка «диск ↔ состав» — главная проверка контура
```bash
node "$QG/tools/xml/orphan-check.mjs" <каталог выгрузки>
```
**Почему это первое, что нужно проверять.** Объект метаданных, лежащий на диске, но не
внесённый в секцию `ChildObjects` файла `Configuration.xml`, **не попадает в собранный
артефакт**. При этом:
- загрузка конфигурации из файлов такие файлы молча игнорирует и не разрешает ссылки на них
вглубь BSL — сборка проходит «успешно», лог пуст;
- валидаторы структуры это тоже не ловят: они проверяют порядок **уже зарегистрированных**
объектов и про файл вне состава ничего не знают.
Итог: объект отсутствует в поставке, все проверки зелёные, падение происходит в рантайме у
пользователя. Это слепое пятно всей штатной цепочки, и закрывает его только явная сверка.
Обратное направление не менее важно: имя в составе без файла на диске ломает саму сборку.
**Коды возврата:** 0 — расхождений нет, 2 — есть сироты либо отсутствующие файлы.
**Каталоги вне карты типов** скрипт не додумывает, а выносит в раздел «не проверено»:
молчаливый пропуск здесь означал бы ровно ту дыру, ради которой проверка и написана.
### Как чинить
Найденную сироту **регистрируют**, а не пересоздают: добавляют строку `<Тип>Имя</Тип>` в
нужную группу `ChildObjects`. Пересоздание объекта средствами генерации перезапишет его XML и
может затереть уже описанные реквизиты, измерения и ресурсы.
---
## 2. Уникальность UUID — вторая проверка того же класса
```bash
node "$QG/tools/xml/uuid-unique.mjs" <каталог выгрузки>
```
Объект, скопированный вместе со своим `uuid`, и заглушка вида `a1b2c3d4-…` дают два разных
объекта с одним идентификатором. Платформа при загрузке либо отвергает выгрузку, либо
оставляет один из двух — второй исчезает бесшумно, ровно как файл-сирота. Валидаторы
структуры это пропускают по устройству: каждый разбирает свой файл и проверяет формат GUID,
а совпадение с соседним файлом лежит за пределами его области зрения.
Проверяются файлы с корневым элементом `MetaDataObject`; повтор внутри одного файла — тоже
находка, продублированный блок реквизита уносит с собой `uuid` оригинала.
**Графические схемы не читаются.** Это измерено, а не предположено: на полной выгрузке УТ
(19 693 файла метаданных, 63 837 идентификаторов) дублей между объектами ноль, а все повторы
пришлись на `Ext/Flowchart.xml` — точки карты маршрута бизнес-процесса, которые платформа
копирует между процессами. Скан схем давал бы находку на каждой типовой конфигурации.
**Коды возврата:** 0 — дублей нет, 2 — есть дубли либо каталог не прочитан.
**Как чинить:** менять UUID у **нового** объекта, а не у исходного. Правка идентификатора
существующего объекта в рабочей базе равносильна его удалению и созданию заново — ссылки на
него теряются.
---
## 3. Валидация структуры файлов
**Путь передавай параметром `-Path`** — он принимается всеми валидаторами без исключения:
```bash
python "$QG/tools/xml/meta-validate.py" -Path "<путь>"
```
У каждого скрипта есть ещё собственное имя параметра, и они разные (`-ObjectPath`,
`-FormPath`, `-RightsPath`, `-SubsystemPath`, `-TemplatePath`, `-CIPath`, `-ConfigPath`,
`-ExtensionPath`). Не подставляй имя от одного скрипта другому: `allow_abbrev=False`, и
вызов упадёт с ошибкой разбора аргументов. `-Path` снимает вопрос целиком.
| Что проверяется | Валидатор | Что передавать |
|---|---|---|
| Объект метаданных (справочник, документ, регистр, перечисление) | `meta-validate.py` | файл `Объект.xml` |
| Управляемая форма | `form-validate.py` | файл `Form.xml` |
| Схема компоновки данных | `skd-validate.py` | файл макета СКД |
| Роль и права | `role-validate.py` | любое из трёх: `Roles/Имя.xml`, каталог `Roles/Имя`, `Roles/Имя/Ext/Rights.xml` |
| Подсистема | `subsystem-validate.py` | файл подсистемы |
| Макет табличного документа | `mxl-validate.py` | файл макета |
| Командный интерфейс | `interface-validate.py` | файл командного интерфейса |
| Конфигурация целиком | `cf-validate.py` | корень выгрузки |
| Расширение конфигурации | `cfe-validate.py` | корень расширения |
| Внешняя обработка или отчёт | `epf-validate.py` | корень исходников обработки |
Роль — единственный объект, где проверяется не файл объекта, а `Rights.xml`; валидатор
приводит к нему любую из трёх форм пути сам. До версии 0.4.3 файл `Roles/Имя.xml` не
распознавался и разбирался как `Rights.xml`, давая **ложную ошибку** при меньшем числе
проверок — то есть выглядел как находка в чужом коде.
Флаги: `-Detailed` — подробный вывод, `-MaxErrors N` — ограничение числа сообщений,
`-OutFile <путь>` — вывод в файл.
**Если Python недоступен** — запиши `[qg skipped: layer=xml, scope=structure-validation,
reason=python_unavailable]` и всё равно выполни пункт 1: сверка диск↔состав работает на Node и
от Python не зависит. Она же и есть самая ценная часть контура.
**Если валидатор упал с `ModuleNotFoundError: No module named 'lxml'`** — это не находка в
проверяемом коде, а недоступность инструмента: все валидаторы разбирают XML через `lxml`.
Запиши `[qg skipped: layer=xml, scope=structure-validation, reason=lxml_unavailable]`, назови
лечение (`pip install lxml`) и так же выполни пункт 1. Молча выдать это за ошибку файла —
худший исход: правка пойдёт в исправный XML.
---
## 4. Права на объекты в ролях расширения
Для расширений, содержащих собственные роли: **собственный объект расширения без явно
выданных прав невидим пользователю**, и ни сборка, ни валидаторы этого не показывают.
Проверяй, что для каждого нового объекта расширения права заданы в файле прав роли. Симптом
пропуска — объект существует, механизм работает, но у пользователя пустой список или
отсутствующая команда.
**Контр-сигналы — где отсутствие прав законно.** Прежде чем выпускать находку, сверься с
`references/role-rights-model.md`:
- у `Перечисление` и `РегламентноеЗадание` объектных прав **не существует** — пустой набор
здесь никогда не находка, а запись о правах в роли, наоборот, дефект;
- право `Use` у HTTP- и веб-сервисов выдаётся точке вызова
(`HTTPService.<Имя>.URLTemplate.<Шаблон>.Method.<Метод>`, `WebService.<Имя>.Operation.<Имя>`),
а не сервису целиком;
- отсутствующий `Ext/Rights.xml` — валидное состояние пустой роли, а не потерянный файл.
**Собственное против заимствованного.** Права требуются собственным объектам и собственным
реквизитам расширения; у заимствованных они наследуются от конфигурации. Признак
принадлежности задан **отсутствием** тега, а не пометкой — разбор в
`references/cfe-object-belonging.md`.
---
## 5. Дефекты, которые проходят валидацию
Валидаторы разбирают XML по схеме формата и молчат о конструкциях, законных по схеме, но
ломающих платформу. Такой дефект опаснее обычного: отчёт зелёный, а артефакт не собирается.
**`AutoCommandBar` таблицы с `Autofill` и вложенным `ExtendedTooltip`.** Загрузка внешней
обработки или отчёта уходит в бесконечный цикл со 100% CPU — не ошибка и не диалог, а
зависание. `form-validate.py` при этом даёт «OK».
```xml
<!-- Вешает загрузку -->
<AutoCommandBar name="ТаблицаКоманднаяПанель" id="10">
<Autofill>false</Autofill>
<ExtendedTooltip name="…" id="11"/>
</AutoCommandBar>
<!-- Законные формы -->
<AutoCommandBar name="ТаблицаКоманднаяПанель" id="10"/>
<AutoCommandBar name="ТаблицаКоманднаяПанель" id="10">
<ChildItems>…кнопки и группы…</ChildItems>
</AutoCommandBar>
```
**Контр-сигнал:** `Autofill` и вложенный `ExtendedTooltip` законны у `CommandBar` —
это отдельный элемент формы, и там конструкция встречается штатно. Признак действует только
внутри `AutoCommandBar` **таблицы**.
**Заявленная неточность:** в том же разборе снят заодно `<TitleLocation>Top</TitleLocation>` у
`Table`, но отдельно он не изолирован. Как самостоятельная находка не выпускается — только как
кандидат при продолжении бисекции.
**Правило времени вместо ожидания.** Загрузка обработки на пустой базе — секунды; на рабочей
базе штатное время порядка семи-восьми минут. Прогон дольше трёх-пяти минут **на пустой базе**
означает зацикливание: процесс снимают и бисектят форму по группам элементов, а не ждут.
---
## 6. Типовые дефекты структуры
| Дефект | Признак | Чем ловится |
|---|---|---|
| Файл-сирота | объект на диске вне состава | пункт 1 |
| Отсутствующий файл | имя в составе без файла | пункт 1 |
| Дубль UUID | два объекта или реквизита с одним идентификатором | пункт 2 |
| Нарушен порядок объектов в составе | несоответствие каноническому порядку типов | `cfe-validate.py` |
| Невалидные элементы формы | элементы вне схемы формата | `form-validate.py` |
| Рассогласованные версии формата | разные версии в связанных файлах | `meta-validate.py` |
| Обработчик формы без процедуры | `qg:XML-FORM-HANDLER-MISSING`: `<Event>` называет имя, которого нет ни в модуле формы, ни в модуле базовой формы. Открытию формы это не мешает (проверено на платформе) — дефект спит до наступления события, отсюда 🟠, а не блокировка | `form-validate.py` |
| Действие команды без процедуры | `qg:XML-FORM-ACTION-MISSING`: то же для `<Action>` команды формы | `form-validate.py` |
| Отсутствие хранилища вариантов у отчёта | не задано хранилище настроек | `epf-validate.py` |
| Права объекта не заданы в роли | объект расширения не виден пользователю | пункт 4 |
| Права выданы типу, у которого их нет | запись `Enum.*` или `ScheduledJob.*` в файле прав | `role-validate.py`, пункт 4 |
| Неполное заимствование | `Adopted` без `ExtendedConfigurationObject` | `cfe-validate.py`, пункт 4 |
| Зависание загрузки на командной панели | `Autofill` и `ExtendedTooltip` внутри `AutoCommandBar` таблицы | пункт 5, валидаторами не ловится |
| Параметр СКД против виртуальной таблицы | `qg:SKD-PARAM-VT-COLLISION`: параметр `Период`/`НачалоПериода`/`КонецПериода` типа `StandardPeriod` при периодической ВТ без явных слотов — «Несоответствие типов» при формировании | `skd-validate.py` |
| Недопустимое поле в выборке группировки СКД | `qg:SKD-GROUP-NONAGGREGATE-FIELD`: поле — не поле группировки (с учётом родителей и реквизитов) и не ресурс — полный отказ формирования | `skd-validate.py` |
| Группировка СКД без выбранных полей | `qg:SKD-GROUP-EMPTY-SELECTION`: ни полей, ни Авто — запрос выполняется, отчёт молча пуст (предупреждение) | `skd-validate.py` |
---
## Записи следа
```
[qg applied: layer=xml, scope=registration-check, ids=[qg:XML-ORPHAN], verdict=violation:qg:XML-ORPHAN]
[qg applied: layer=xml, scope=uuid-uniqueness, ids=[qg:XML-UUID-DUP], verdict=clean]
[qg applied: layer=xml, scope=structure-validation, ids=[qg:XML-STRUCT], verdict=clean]
[qg applied: layer=xml, scope=form-binding, ids=[qg:XML-FORM-HANDLER-MISSING,qg:XML-FORM-ACTION-MISSING], verdict=clean]
[qg skipped: layer=xml, reason=not_applicable]
```
**Первые три строки печатают сами инструменты** — переноси их вывод дословно. Запись
`structure-validation` печатает любой из валидаторов XML (`meta-`, `form-`, `role-`, `skd-` и
остальные семь): проверка структуры называется одним именем независимо от вида файла. Каждый
инструмент отмечается в журнале прогонов, и валидатор следа сверяет: запись `applied` по
проверке, инструмент которой не запускался, снятие гейта не пройдёт.
Семантические находки СКД (`qg:SKD-PARAM-VT-COLLISION`, `qg:SKD-GROUP-NONAGGREGATE-FIELD`,
`qg:SKD-GROUP-EMPTY-SELECTION`) печатает тот же `skd-validate.py` внутри прогона
`structure-validation` — отдельного вызова для них нет. **Тексты запросов в `<query>` схемы
он не разбирает** — их проверяет `query-lint.mjs` контура `code`, которому изменённые XML
передаются наравне с `.bsl`.
Формат — `../quality-gate/references/evidence-format.md`.
**Валидность структуры не означает компилируемость.** Валидаторы разбирают XML и не
компилируют тела модулей; синтаксическая ошибка внутри процедуры проходит их все. Если
проверка конфигурации платформой не запускалась, нужна запись
`[qg not_verified: dimension=compilation, reason=no_platform]`.
---
## Принципы
- **Регистрация проверяется раньше структуры.** Идеально валидный XML вне состава бесполезен.
- **Сверка идёт в обе стороны.** Сирота ломает рантайм, отсутствующий файл ломает сборку.
- **Зелёная сборка ничего не доказывает.** Загрузка конфигурации из файлов игнорирует
незарегистрированное молча.
- **Сироту регистрируют, а не пересоздают** — пересоздание затирает содержимое объекта.
- **Отсутствие прав — не всегда упущение.** У части типов объектных прав не существует, и
требование выдать их отправляет искать несуществующую настройку.
- **Валидатор молчит и о законном, и о неразобранном.** «OK» на форме, которая вешает
загрузку, — не вердикт о работоспособности, а граница схемы формата.
> `$QG` — каталог установленного плагина. Как его разрешить (переменная `CLAUDE_PLUGIN_ROOT`
> в оболочке пуста) — см. раздел «Путь к инструментам плагина» в навыке `quality-gate`.