Наполнение макета по свежему пути (Templates/<Имя>/Ext/Template.xml) на
PowerShell падало с «Could not find a part of the path». py-порт каталог
создаёт, form-compile и skd-compile — тоже; не создавал только этот порт.
Тестами не ловилось: во всех кейсах mxl-compile писал в уже существующий
каталог. Добавлен кейс с записью по несуществующему пути — проверено, что на
коде до правки он падает.
Найдено при разборе вопроса, нужна ли заглушка тела макета в template-add:
оказалось, заглушка держала не замысел, а этот дефект.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Из ~46 навыков-эмиттеров XML восемь не имели ни одной проверки `preserves`:
form-edit, form-remove, meta-remove, skd-edit, template-remove, xdto-edit,
support-edit, cfe-patch-method. Шесть из них правились в предыдущем коммите,
и канон у них держался на том, что правка была механической, а не на проверке.
Ожидания писались ПО КАНОНУ, а не по текущему поведению — и это сразу вскрыло
три дефекта:
1. form-remove очищал слот формы через InnerText="" / text="" — получалась
пустая пара <DefaultObjectForm></DefaultObjectForm>. Платформа пустых пар
не пишет (0 на 476 942 XML). Теперь IsEmpty / text=None.
2. Опустевший <ChildObjects> оставался парой, разнесённой по строкам, —
в PS-порту form-remove и template-remove. Корпус: 1394 самозакрывающихся
<ChildObjects/> на acc+erp, пустых пар 0 в обеих формах.
3. template-add писал пустой макет парой <SpreadsheetDocument></...>.
Во всей выгрузке acc_8.3.27 (65 040 XML) многострочных пустых пар нет
ни для одного тега.
Заодно усилена сама проверка noEmptyPairs: она ловила только СМЕЖНЫЕ теги,
поэтому дефект №2 проходил мимо неё. Добавлен вариант с переводом строки
внутри; дискриминатором служит сам перевод строки — значащий пробельный
текст-узел (<xr:FillValue xsi:type="xs:string"> </xr:FillValue>) его не
содержит и под проверку не попадает.
support-edit покрыт частично (BOM у ParentConfigurations.bin — проверено, что
платформа пишет его с BOM во всех трёх выгрузках), cfe-patch-method — BOM+EOL
у .bsl: хвостовой перевод строки у модулей неканоничен (1235 с ним, 766 без),
поэтому не утверждается.
Регресс: 647/647 ps1, 644/647 py (3 skipped). Эталоны переснятые: три места,
каждое — ровно ожидаемая пара строк.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Все 23 проверки preserves.eol ожидали crlf — ни одного LF-исходника в наборе
не было, поэтому расхождение портов по переводам строк жило целиком в
непокрытой ветке и не могло быть замечено.
Добавлены LF-фикстуры и кейсы roundtrip-lf-preserve:
- cf-edit, meta-edit, subsystem-edit — правка существующего файла через
XmlWriter (наследование стиля исходника);
- role-compile, meta-compile, xdto-compile — регистрация в существующем
Configuration.xml. Эти кейсы проверяют обе половины правила разом: правка
наследует LF, а создаваемый рядом файл получает канон CRLF.
Проверено в требуемом порядке: на коде ДО правки кейсы падают
(role-compile/meta-compile/xdto-compile — «expected lf, got 251 CRLF»,
meta-edit — «40 CRLF + 136 lone LF», subsystem-edit — «1 CRLF + 24 lone LF»),
после правки проходят на обоих рантаймах.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Три остатка волны #57, все в одной точке — блоке записи XML.
1. Порты расходились по EOL. PS гонял документ через XmlWriter с дефолтным
NewLineHandling.Replace и принудительно переводил весь файл в CRLF, а py
сохранял стиль исходника: role-compile на LF-ном Configuration.xml давал
10311 байт против 10062. Ещё в пяти навыках None стоял, но финальной
нормализации не было, и на LF-исходнике выходил смешанный EOL (meta-edit:
40 CRLF + 136 одиночных LF) — та же форма, что у исходного дефекта #57.
Приведено к форме cf-edit.ps1: None + канонизация к LF + целевой перевод
строки (стиль файла-назначения, для создаваемого файла — канон CRLF).
Replace не годится как замена: он превращает переводы строк внутри значений
атрибутов в .
2. Гард `if (-notmatch CDATA)` не обрабатывал CDATA, а отказывался от канона
во всём файле — то есть деградировал до «не сделал ничего». Заменён
альтернацией: участки CDATA и комментариев возвращаются как есть, замена
идёт только вне них. На реальных данных поведение не меняется — в корпусе
из 476 942 XML нет ни одного CDATA и ни одного комментария.
3. py: три реализации одного правила детекта EOL сведены к одной. Мажоритарное
правило в role-compile/subsystem-compile давало ДРУГОЙ ответ на смешанном
входе. Дефолты _finalize_xml_bytes для нового файла приведены к канону
(UTF-8, CRLF, без хвостового перевода); meta-edit срезает хвост в обеих
ветках, а не только при создании файла.
Попутно, найдено байтовой сверкой портов:
- py вставлял <Role>/<Form>/<Subsystem> с пятью табами вместо трёх —
подстановка по голому </ChildObjects> удваивала отступ строки; снэпшоты
этого не видели, так как схлопывают пробелы между тегами;
- py-порты xdto-* писали XML-декларацию одинарными кавычками (так отдаёт
lxml), платформа и PS пишут двойные.
Регресс: runner 647/647 ps1, 644/647 py (3 skipped), дрейфа снэпшотов нет.
Корпусный раундтрип метаданных: 4897 объектов, match 100%, совпадает с
эталонами захода #57.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Код нормализации пустого тега повторён в 24 местах, а комментарий к нему за время
работы над #57 расплодился в шесть вариантов формулировки. Это то же расхождение
копий, что и в коде, только в тексте: читающий не понимает, значима разница или нет.
Сведено к одной паре формулировок — про сам тег с гардом и про запись через
MemoryStream. Комментарий теперь начинается с ПРАВИЛА, а не с причины: раньше
верхняя строка рассказывала, что here-string наследует LF от .ps1, и читалось это
как «переводы строк берём из скрипта навыка» — тогда как правило обратное:
создаём файл — пишем канон, правим существующий — наследуем его стиль.
Два пояснения в cfe-borrow унификация чуть не стёрла, они возвращены: источником
там служит OuterXml, а не XmlWriter, и нормализация стоит после вставки реквизитов,
чтобы накрыть и их. Это причина местного отличия, а не дубль.
Ссылки на ишью прорежены: #44/#46/#47 осталась по одной на файл — там, где правило
формулируется, — и убрана оттуда, где была эхом. #57 в скриптах не было вовсе.
Заодно выровнены две мелочи, найденные той же сверкой копий: interface-edit в
py-порте не обрезал хвостовой перевод там, где PS обрезает (поведение совпадало,
код — нет), а cf-edit нормализовал EOL инлайном вместо общей двухшаговой формы.
Версии подняты в ОБОИХ портах всех затронутых навыков, включая те, где текстуально
менялся только .ps1: номер версии в этом проекте читается как «одно и то же
поведение», и разводить его из-за комментария нельзя. Паритет проверен — 69 пар,
ноль расхождений.
Windows 641/641 обоими портами.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
skd-edit нормализовал ` />` в `/>` без проверки на CDATA и комментарии, в отличие
от остальных 23 мест, где такая нормализация есть. Вскрылось сверкой копий:
конструкция повторяется в 24 местах, и вариантов формы должно быть ровно два —
с гардом и никак иначе.
Гард нужен потому, что `>` не экранируется только внутри CDATA и комментариев —
там ` />` может оказаться содержимым, а не концом тега. В 1С-метаданных ни того,
ни другого не встречается (0 из 476 943 XML корпуса), так что практического
расхождения поведения нет — но полагаться на это в одном месте и защищаться
в остальных двадцати трёх непоследовательно.
Правка в обоих портах: у py-порта нормализация тоже была без гарда (она там
защитная — lxml и так пишет плотно).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Навыки автономны, общей библиотеки нет — значит копии функции записи должны быть
буквально одинаковыми, иначе теряется смысл копипасты. PS-порт дисциплину держал
(7 копий байт-в-байт), а py разъехались: три варианта имени параметра, два стиля
кавычек, у cfe-borrow ещё и другое имя функции (save_xml_file).
Сведено к одной форме: имя write_xml_file, параметр content, одинаковое тело.
Различаются ровно две вещи, и обе осмысленные: вызов локального базового writer'а
(имена исторические — write_utf8_bom / write_text_with_bom / save_text_bom) и одна
строка примечания там, где у навыка есть исключение (модуль .bsl, HTML).
Текст комментария в PS выровнен с docstring'ом py: раньше он описывал ПРИЧИНУ
(here-string наследует LF), а не правило, и после правки py ports разъехались по
смыслу. Синтаксис остаётся разным по природе языков: в py docstring (в портах их
421 против 73 функций с #-комментарием — это местная норма), в PS — #.
Windows 641/641 обоими портами.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Скелетные генераторы писали Module.bsl с LF, а платформа пишет модули с CRLF:
в выгрузке БП 2643 CRLF и чисто-LF 0 из 3001 модуля (358 «смешанных» — LF внутри
строковых литералов кода), BOM 2001/2001. По тому же доводу сходимости, что и для
XML: платформа перепишет модуль в CRLF при первой выгрузке, значит наш LF — дифф,
созданный нами.
В репозитории уже была встречная конвенция: meta-compile (модуль команды) и
cfe-patch-method (base/local/remote.bsl) пишут .bsl явным CRLF в обоих портах.
Выбивались только эти три скелета.
Хвостовой перевод строки НЕ трогаем: у платформы он неканоничен — 1235 модулей
с ним, 766 без; это след автора кода, а не правило. Меняем только разделители.
Правка в точке записи, а не в шаблоне: так одна строка на скрипт и не трогаются
\u-экранированные кириллические литералы в py (Edit по ним переэкранирует).
Снэпшоты нормализуют EOL и увидеть это не могут — добавлен preserves на модуль
в epf-init/basic, erf-init/basic, form-add/basic.
Windows 641/641 обоими портами, мак 590/0/51, дрейфа снэпшотов нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Тот же writer без newline="", что чинился в form-add.py, остался ещё в четырёх
py-портах, и пишут они не модули, а 1С XML: заимствованные объекты и метаданные
формы (cfe-borrow), Template.xml и XML-макеты (template-add), Help.xml (help-add),
вновь создаваемый CommandInterface.xml (interface-edit).
Контент там собирается литералами с \n, поэтому текстовый режим Python давал CRLF
на Windows — «случайно правильно» — и LF на macOS, ломая канон #57 ровно там, где он
только что установлен. Проверено на маке ДО фикса: cfe-borrow 0 CRLF + 33 одиночных
LF, template-add 0 + 2, interface-edit 0 + 5. Порчи \r\r\n не было, но это везение:
подай такой writer CRLF-контент — каждая строка стала бы \r\r\n.
Почему не поймали раньше: аудит гонялся на Windows, где текстовый режим даёт канон;
снэпшоты нормализуют EOL; байтовый preserves ни разу не проверялся на маке.
Разделены два пути, которые я сперва смешал в cfe-borrow:
- save_xml_file — канон (CRLF, без хвоста) для файлов, которые СОЗДАЁМ;
- save_text_bom — пишет как есть, для файлов, которые ПРАВИМ: переводы строк уже
пришли из самого файла и менять их нельзя (контракт #44/#46/#47). Туда же
добавлено чтение с newline="" — иначе CRLF терялся ещё на входе.
interface-edit чинится и в PS-порте: ветка -CreateIfMissing давала смешанный EOL
(3 CRLF + 2 одиночных LF) уже на Windows. Аудит #57 её пропустил, потому что
фильтровал файлы по расширению .xml, а кейс создаёт файл с именем без расширения.
Канон, как выяснилось, зависит от типа артефакта: XML — CRLF, текстовый макет —
CRLF, а HTML-макет платформа хранит с LF (корпус: 399 LF из 400). Поэтому HTML
через канон-writer НЕ идёт.
preserves добавлен четырём навыкам (у них его не было вовсе) + отдельно на Help.xml.
Именно прогон на маке и даёт покрытие: на Windows эти кейсы зелены и без фикса.
Windows 641/641 обоими портами, мак 590/0/51, дрейфа снэпшотов нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
В .ps1 шапка называла НАВЫК, в .py — ФАЙЛ (help-add v1.12 против add-help v1.12).
Канон — имя навыка: так называется каталог, так навык вызывается, так он записан
в SKILL.md. Расхождение было только у этих четырёх: у них имя файла отличается от
имени навыка. Версии совпадали, поменялось только имя.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Раннер сам прятал дефекты, которые чинит #57. normalizeXmlContent при
runtime=python снимал ровно четыре измерения: пробел перед `/>`, whitespace между
тегами, пустую пару <Tag></Tag> и хвостовой пробельный мусор. Паритет PS<->PY по
этим измерениям проверялся ЧЕРЕЗ маску — расхождение физически не могло упасть.
Снято три из четырёх (пробел, пустая пара, хвост). Схлопывание whitespace между
тегами оставлено: порты кое-где расставляют отступы иначе, это форматирование, а
не байтовый канон.
Ужесточён checkPreserves: eol теперь считает ОДИНОЧНЫЕ LF, а не «есть ли хоть один
CR». Прежняя проверка пропускала смешанный выход — cfe-init давал 10 CR на 70
строк и проходил её, то есть головной дефект тикета был ей невидим. Добавлены
ключи selfClose:"tight" и noEmptyPairs; preserves проставлен в 13 кейсах
навыков-эмиттеров (по одному на навык, на его СОБСТВЕННЫЙ артефакт).
Снятие масок сразу вскрыло четыре реальных расхождения портов:
- form-edit собирает выход из OuterXml и писал `<a />`; в списке 17 навыков его не
было, потому что искал по вызовам Save — здесь другой путь. Тот же случай, что
с Form.xml в cfe-borrow;
- meta-edit py дописывал хвостовой перевод в создаваемый Ext/Predefined.xml и
читал существующий без newline='' (терял CRLF);
- skd-info py писал отчёт -OutFile без хвостового перевода, PS через WriteAllLines
— с ним. Это текстовый отчёт, канон Конфигуратора к нему не относится, поэтому
выровнял py по существующему эталону;
- фикстуры кейсов содержали <Vendor></Vendor> — снимок нашего же старого вывода.
Конфигуратор пустых пар не пишет, .NET их сохраняет, lxml схлопывает. Поправлены
10 фикстур: пары → самозакрывающиеся, EOL и BOM не тронуты.
Результат: PS 641/641, python 638/641 (+3 runtimeOnly-скипа) — со снятыми масками.
Дрейф снэпшотов: 10 файлов, только пробельные теги и пустые пары.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Конфигуратор пишет <Vendor/>, а не <Vendor></Vendor>: в 8 выгрузках cfsrc
(476 943 XML) пустых пар ноль. Шаблоны подставляли значение ВНУТРЬ пары, поэтому
при пустом значении получалась пара.
Собираем элемент целиком: <Vendor>/<Version> (cf-init, cfe-init),
<DefaultRoles> (cfe-init при -NoRole), <Key> и <Namespace> (meta-compile —
план обмена и веб-сервис). Образец рядом же: <Comment> и <Description> в
meta-compile так делали и раньше.
Дефект был в ОБОИХ портах одинаково — здесь дело не в сериализаторе, а в шаблоне.
Пустая пара <Namespace> в cfe-borrow была не его: он копирует свойства
заимствуемого объекта, а порождал их meta-compile.
Дрейф снэпшотов: 340 <Vendor/>, 339 <Version/>, по одному <Key/>, <Namespace/>,
<DefaultRoles/>. Содержательных изменений ноль. Тесты 641/641.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Головной дефект тикета: cfe-init выдавал Configuration.xml с 10 CR на 70 строк, а
роль и язык — вовсе без CR. Теперь 70/70, последний байт `>`.
Правило: меняем только РАЗДЕЛИТЕЛИ строк, содержимое текстовых узлов не трогаем.
Платформа его не трогает тоже — в запросах СКД из чистой выгрузки встречаются и
CRLF, и одиночный LF. Поэтому сплошная нормализация файла применяется только там,
где многострочных текстовых узлов нет по построению (скелеты), а в объектном XML
и формах разделители правятся в точке сборки.
Источников оказалось четыре, а не один:
1. Скелеты (cf-init, cfe-init, epf-init, erf-init, form-add, help-add,
template-add) собираются here-string'ами, а .ps1/.py в репозитории хранятся с
LF — отсюда LF и смешанный EOL.
2. py-порты склеивали документ через '\n'.join(lines); PS в тех же местах давал
CRLF через AppendLine — порты расходились побайтово.
3. Билдеры Predefined в meta-compile собирались с явным LF и даже сворачивали
CRLF→LF из общего эмиттера типов.
4. Чтение существующего файла в python БЕЗ newline='' молча схлопывает CRLF в LF
(универсальные переводы строк), и запись потом кладёт LF. Так role-compile и
subsystem-compile переписывали в LF весь Configuration.xml. meta-compile это
уже делал правильно — там newline='' стоял с фикса #44/#46/#47.
Отдельно: XML-парсер по спецификации схлопывает CRLF при разборе, поэтому
lxml-порты (xdto-compile, xdto-edit) отдавали LF-документ там, где .NET возвращал
CRLF через NewLineHandling. Восстанавливаем EOL исходного файла.
Разделение канона и сохранения стиля:
- файл СОЗДАЁМ — канон (CRLF, без хвоста);
- существующий ПРАВИМ — наследуем его EOL, включая перевод строки вставки
(контракт #44/#46/#47). Иначе LF-проект получал бы смешанные файлы — ровно то,
на что заведён #57. Кейсы roundtrip-crlf-preserve остаются зелёными.
Хелпер записи скопирован в каждый навык (навыки автономны). У meta-compile он
называется Write-XmlFileKeepEol / write_xml_file_keep_eol: там нормализовать EOL
НЕЛЬЗЯ (многострочные запрос, синоним, значение заполнения), и одинаковое имя при
разном поведении было бы ловушкой.
Заодно: имя временного файла батча в meta-compile.ps1 получило GUID. Фиксированное
"meta-compile-batch-$idx.json" в общем %TEMP% сталкивало два параллельных запуска
(«file is being used by another process») — из-за этого полный набор приходилось
гонять с урезанной параллельностью. py-порт уже брал mkstemp.
Аудит: было ~250 файлов с дефектом EOL, стало 0 в обоих портах. Осталось четыре
законных случая — фикстуры roundtrip-crlf-preserve (сохранение стиля) и запрос
динсписка с многострочным текстом. Дрейф снэпшотов — 1 файл. Тесты 641/641.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Конфигуратор не пишет перевод строки в конце файла — последний байт `>`. Это видно
на чистой выгрузке пустой ИБ (Windows и macOS) и на всех 8 выгрузках в cfsrc.
Наши эмиттеры добавляли лишний, причём оба порта и по разным причинам:
- PS: StringBuilder.AppendLine дописывает Environment.NewLine после последней строки;
- PY: '\n'.join(lines) + '\n' — хвост добавлен явно.
В meta-compile заведена единая точка записи Write-XmlFile: там документ собирают
и AppendLine, и явные `n в билдерах Predefined/Content/Flowchart, а выходов семь.
В остальных обрезка стоит в самом вызове записи.
Модули .bsl, JSON-выход form-compile, HTML-справка help-add и XSD из xdto-decompile
сюда НЕ входят: канон Конфигуратора описывает XML метаданных, а у этих артефактов
свои правила. Результаты XmlDocument.Save тоже не трогаются — у них хвоста нет.
Навыки редактирования продолжают сохранять стиль ВХОДНОГО файла: кейс
subsystem-edit/roundtrip-crlf-preserve, где фикстура намеренно с хвостом, остаётся
зелёным. Это контракт #44/#46/#47, и он не отменяется.
Дрейф снэпшотов: 547 файлов, у каждого только последняя строка, содержательных
изменений ноль. Тесты 641/641.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
.NET XmlWriter пишет `<a />`, а Конфигуратор — `<a/>`. Поскольку Save переписывает
файл целиком, meta-edit при добавлении одного реквизита переводил в пробельную форму
все пустые теги документа. Дефект только у PS-порта: lxml (45 из 49 py-портов) уже
пишет плотно, то есть порты расходились побайтово.
Канон измерен, а не предположен: чистая выгрузка пустой ИБ на Windows и macOS плюс
сплошной скан 8 выгрузок в cfsrc — 476 943 XML, 21 294 119 самозакрывающихся тегов,
пробельных 0. Форма не зависит от ОС, версии платформы (8.3.20-8.5) и наличия
атрибутов. Правило уже было реализовано в skd-edit — оттуда и взято.
Замена безопасна доказуемо: .NET экранирует `>` как `>` и в тексте, и в атрибутах,
поэтому ` />` после Save — только конец тега. Лазейки (CDATA, комментарии) в
1С-метаданных не встречаются — 0 из 476 943 файлов; гард на них всё равно стоит.
Одиннадцать скриптов писали прямо в FileStream без пост-обработки — переведены на
MemoryStream, что заодно чинит `encoding="utf-8"` строчными (335 файлов в снэпшотах).
В cfe-borrow правится и сборка Form.xml: куски берутся из OuterXml, а он спацовывает
так же, как XmlWriter.
Дрейф снэпшотов: 12 838 строк тегов + 335 деклараций, содержательных изменений ноль.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README называл info/validate-навыки типичным случаем для noSnapshot: эталон
зафиксировал бы выход preRun, а не проверяемого навыка. Факт верный, вывод — нет.
Когда preRun собирает фикстуру, эталон фиксирует ВХОД теста. Без него дрейф
навыка-генератора меняет фикстуру молча, и ожидание вроде «Составной (6)»
начинает проверяться на другом объекте — либо падает без внятной причины, либо
сходится случайно и перестаёт что-либо проверять. meta-compile такой дрейф даёт
регулярно. Поэтому ни один из семи info-навыков noSnapshot не использует —
у всех эталоны, и это осознанно, а не упущение.
Правило сформулировано явно: есть preRun с генерацией фикстуры → эталон;
нет preRun или фикстура тривиальна → noSnapshot.
В шапке verify-snapshots отмечено, что он работает и с кейсами навыков, которые
сами ничего не пишут, — там он проверяет, что платформа принимает собранную
preRun фикстуру. Плюс предупреждение, что на кейсах setup: external проверка
вырождается в загрузку 1С собственной выгрузки: ~3 минуты на кейс без пользы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Строка «Иерархический: …» печаталась в ветке Catalog, хотя свойство
Hierarchical есть и у планов видов характеристик — в корпусе acc/erp/ut/unf
таких 13. У них иерархия не выводилась ни в overview, ни в full.
Хуже того, вывод противоречил сам себе: блок стандартных реквизитов в full
показывал Родитель (он определяется по тому же Hierarchical), а строки про
иерархию рядом не было — родитель появлялся ниоткуда.
Иерархия вынесена из ветки Catalog и печатается по наличию свойства;
подчинение осталось специфичным для справочника. Отсутствие иерархии
по-прежнему не печатается: сообщаем о наличии, молчим об отсутствии —
тихую ошибку в коде даёт только необнаруженное наличие.
Свойство Hierarchical проверено по корпусу: оно есть только у Catalog и
ChartOfCharacteristicTypes (у плана счетов иерархия по природе, свойства нет).
Прогон по корпусу: ровно 13 изменённых строк, побочных эффектов нет.
Находка из отклонённого PR #59 — там иерархия тоже была вынесена из ветки
Catalog. Реализация не заимствована: PR печатал «Иерархический: нет» для
неиерархических объектов, то есть строку-шум примерно у 4000 объектов.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Свёртку составного типа и её разворот через drill-down проверял только кейс
на выгрузке acc_8.3.24, а он привязан к локальному пути и на macOS
пропускается — то есть ключевая новая логика там не проверялась вовсе.
Добавлены два синтетических кейса на фикстуре от meta-compile: ПВХ с шестью
типами сворачивается в «Составной (6)» и печатает подсказку, а -Name
ТипЗначения разворачивает список. В составе фикстуры два v8:TypeSet
(cfg:CatalogRef, cfg:DocumentRef), поэтому кейсы заодно покрывают разбор
обобщённых ссылочных типов.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ишью #58: сводка не показывала владельца подчинённого справочника, и код,
написанный по ней, падал на Записать(). Владелец оказался частным случаем:
шапка «Код(9) | Наименование(100)» смешивала свойства объекта с полями и не
давала по полям ни типа, ни обязательности. По acc_8.3.24 из 690 справочников
23 имеют числовой код, 122 — фиксированной длины, 426 — вовсе без кода; всё
это было невидимо. У ПВХ, планов счетов и обмена, бизнес-процессов и задач
шапки не было вообще — включая ТипЗначения у ПВХ.
Поля вынесены в блок «Стандартные реквизиты» на языке блока «Реквизиты»
(имя — тип — [флаги]), в шапке остались свойства объекта. В overview
показываются те, что нельзя вывести из остального вывода: Владелец, Код,
Наименование, Дата, Номер, ТипЗначения. Ссылка, ПометкаУдаления, Родитель,
ЭтоГруппа следуют из типа объекта и строки «Иерархический» — они в full.
Обязательность берётся из StandardAttributes/FillChecking. Блок в XML
опционален (meta-dsl-spec §7.1.1), поэтому при его отсутствии действует
профиль платформенных дефолтов, синхронный с meta-compile — иначе объекты,
созданные meta-compile, теряли бы флаг, то есть ровно кейс из ишью.
Составной ТипЗначения ПВХ доходит до 151 типа и сворачивается в счётчик
«Составной (110)»; полный состав даёт drill-down -Name ТипЗначения, о чём
сообщает единственная строка в конце вывода. brief получил строку
«Подчинён: …» — факт структуры без обещаний про обязательность, которых
brief выполнить не может.
Проверено сравнением с эталонным прогоном по 4768 объектам acc/erp/ut/unf:
все расхождения классифицированы, необъяснённых нет, ненулевых кодов возврата
нет. Владелец появился у 242 объектов, числовой код — у 56.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ветка TypeSet в Format-Type распознавала только DefinedType и Characteristic,
а остальные наборы отдавала в вывод как есть. Голое имя метатипа без «.Имя»
означает все ссылки этого класса (docs/meta-dsl-spec.md), и такие типы
печатались сырыми — 254 вхождения в 82 строках на корпусе acc/erp/ut/unf:
ЗначениеПустойСсылки cfg:ExchangePlanRef | cfg:BusinessProcessRef | …
Свойство1 Булево | Строка(128) | ДатаВремя | cfg:AnyIBRef
Разбор вынесен в Format-SingleTypeSet: AnyRef → ЛюбаяСсылка, AnyIBRef →
ЛюбаяСсылкаИБ, голые XxxRef — через существующий refTypeMap. Конкретный тип
всегда пишется с точкой, поэтому «СправочникСсылка» без точки однозначно
читается как обобщённый.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Get-SimpleChildren брала InnerText дочернего узла ChildObjects. Для Form и
Template это верно — имя лежит прямо в тексте узла. Command же имеет вложенный
Properties, и InnerText склеивал всё его содержимое в одну строку:
Команды: БанковскиеСчета_ИнтерфейсИнтеграцииСБанкомruБанковские счетаForm
NavigationPanelImportantcfg:CatalogRef.ОрганизацииSinglefalseAutoAuto
Теперь имя берётся из Properties/Name, если оно есть, иначе — прежний InnerText.
Задеты обе точки вывода: overview у отчётов и обработок и full у всех типов.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Заимствованные DataProcessor, Report, DocumentJournal, HTTPService,
WebService, Subsystem и др. выводились без <ChildObjects/> — платформа
отвергала расширение при загрузке исходников («ожидаемое ChildObjects»).
Признак теперь берётся у объекта-источника, список типов остаётся
страховкой и дополнен недостающими.
Следом всплывал второй отказ — «Отсутствует внутренняя информация
(узел InternalInfo)» для Sequence, FilterCriterion, SettingsStorage:
этих типов не было в карте генерируемых типов. Добавлены они, а также
IntegrationService и WSReference.
Проверено реальной загрузкой на 8.3.24 и 8.3.27: синтетический стенд
(9 типов) и заимствование из acc/ut в BP_DEMO и UT_DEMO.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Раундтрип по 1173 пакетам XDTO трёх конфигураций дал 760 совпадений и 413
расхождений — все ровно по две строки и все на УТ: штамп версии. Оригинал
2.20, наш вывод 2.17. Содержательных расхождений нет ни одного.
xdto-compile единственный из навыков, пишущих XML внутрь конфигурации, не
определял версию формата. Причина не в XDTO: волна авто-детекта прошла
2026-04-06 (d1550864) и накрыла существовавшие тогда навыки, а xdto-compile
появился 2026-07-25 — соглашение к нему просто не применили.
Добавлен Detect-FormatVersion / detect_format_version — дословная копия из
остальных навыков (включая сегодняшнюю правку про символы вместо байтов),
разрешение пути на вызывающей стороне.
Аудит остальных навыков: литеральный хардкод остался только в epf-init и
erf-init (автономные обработки, конфигурации рядом нет — дефолт законен) и в
тестовой заглушке stub-db-create.ps1.
Проверка: XDTO 1173/1173 без расхождений (было 760), сюита xdto 43/43 на
обоих портах.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Detect-FormatVersion читает префикс Configuration.xml как
ReadAllText(...).Substring(0, Min(2000, (Get-Item).Length)). Размер файла — в
БАЙТАХ, Substring считает СИМВОЛЫ: на кириллице байт больше, и если файл
короче 2000 символов, длина среза выходит за строку и навык падает
исключением. Проявлялось на маленьких конфигурациях — например на фикстуре
EPF внутри конфигурации на поддержке.
Функция расходится копиями по навыкам, поэтому правка одинаковая в восьми:
длина берётся по самой строке. В help-add так было изначально; в
meta-compile рядом (Detect-CompatibilityMode) уже стоял верный вариант.
py-порты иммунны: там f.read(2000) в текстовом режиме — читает символы и не
бросает исключение. Зеркалить нечего, версии выровнены по обоим портам.
Заодно tests/skills/verify-snapshots.mjs: outputPath кейса читался только из
params, тогда как runner.mjs берёт его с верхнего уровня — из-за расхождения
харнесс искал результат не там, где навык его написал, и это маскировало
падение как «выход не создан».
Проверка: 1С-сертификация mxl-compile 13/13 (кейс guard-allow-external падал
с начала кампании), полная сюита 630/630 на PowerShell и 627+3 skipped на python.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Правило платформы единое и подтверждено дважды. По корпусу трёх конфигураций:
92142 сырых кавычки в тексте элементов и НИ ОДНОЙ "; в условиях RLS —
16334 сырых против нуля экранированных (при этом & платформа пишет, то есть
амперсанд экранируется, а кавычка нет). Загрузка на стенде: оба варианта
принимаются, но выгружает платформа сырую кавычку — то есть " не ошибка,
а лишний шум в роундтрипе.
Решение было принято раньше в form-compile (там оно и записано комментарием) и
в skd-compile/skd-edit, но шесть навыков из него выпали. Приведены к общему виду:
где Esc-Xml использовался только для текста — функция стала текстовой; в meta-edit,
где она нужна и для атрибута, текстовые места переведены на существующий
Esc-XmlText. Атрибуты нигде не затронуты: там экранирование кавычек обязательно.
Дрейф эталонов — три строки, все условия RLS; role-info и role-validate строят
фикстуры прогоном role-compile (кросс-навыковый пересъём).
Проверка: полная сюита 630/630 на PowerShell и 627+3 skipped на python;
1С-сертификация role-compile 9/9, subsystem-compile 9/9, subsystem-edit 6/6,
form-edit 6/6, meta-edit 16/16. В mxl-compile кейс guard-allow-external падает
и до правки — не связан.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Первый полный прогон по всем поддерживаемым типам трёх конфигураций
(41366 объектов) вскрыл два дефекта, живущих только в корпусе 2.17.
DataPathField в блоке характеристик полиморфно: обычно -1 («не задано»),
но в 8 случаях содержит путь к полю. Обе стороны жёстко приводили значение
к [int], из-за чего декомпиляция всего объекта падала — 8 документов БП не
разбирались вовсе. Теперь число остаётся числом, а путь проходит через
Expand-CharField/Shorten-CharField, как соседние путевые поля.
Текст элемента экранировался атрибутной функцией: кавычка превращалась в
", тогда как платформа держит её в тексте как есть (наименование
«Транспортные средства, зарегистрированные в системе "Платон"» в ERP).
В компиляторе для этого давно есть Esc-XmlText/esc_xml_text — переведены
все 128 мест эмиссии текста в обоих портах. Замена строго ослабляющая:
атрибуты не затронуты, экранирование & < > сохранено.
Проверка: три документа БП с путевым DataPathField 3/3, сюита meta-compile
76/76 на обоих портах. Полный прогон корпуса идёт частями.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сокращённая запись предопределённого элемента — "(Код) Имя [Наименование]".
Компилятор читает код как [^)]*, а имя как \S+, поэтому значение, содержащее
собственные разделители грамматики, разбор ломает: код "114 (108)" даёт
"(114 (108)) Код108", регулярка не сходится, и элемент теряет и имя, и код —
в XML уходят пустые <Name/> и <Code/>.
Декомпилятор теперь проверяет однозначность и при конфликте отдаёт объектную
форму {name, code, description}, которую компилятор поддерживает давно.
Конфликтом считаются ')' или ':' в коде, пробел/скобка/':' в имени, скобки
в наименовании.
Затронуто по 6 элементов в БП и в ERP (вычеты НДФЛ с кодами вида "117 (109)").
Пробел давний: эмиссия predefined помечена в WORKFLOW как черновая, а на УТ
таких кодов нет — вскрылось только на корпусе 2.17.
Проверка: справочники acc+erp+ut 2159/2159 (было 2157), JSON декомпилятора
побайтово совпадает у PS и py, сюиты meta-decompile 3/3 на обоих портах и
meta-compile 76/76.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Аудит всех 198 сравнений в декомпиляторе после того, как ловушка PS
(-eq/-ne регистронезависимы) сработала трижды подряд: синоним метода,
RootURL и синоним шаблона теряли значение, отличавшееся от дефолта
только регистром.
Сравнения разделены на два класса. Рискованный — где дефолт ВЫВОДИТСЯ ИЗ
ДАННЫХ (имя, синоним, обработчик): там регистр реально гуляет. Такие
сравнения синонимов уже были регистрочувствительными с прошлой кампании;
оставались три — обработчик метода против "ИмяШаблона+ИмяМетода", состав
UsePurposes и список InputByString. Исправлены.
Add-EnumProp (центральный хелпер сравнения с дефолтом) тоже переведён на
-cne: на текущем корпусе это no-op, платформа пишет enum канонически, но
снимает латентную ловушку.
Инлайновые сравнения с enum-литералами (~150 шт.) оставлены как есть:
там дефолт — фиксированная константа формата, а не данные, и расхождение
по регистру означало бы неканоничный enum от платформы, чего не бывает.
Проверка (изменений нет ни на одном корпусе): сервисы ERP+БП 46/46,
сервисы УТ 25/25, справочники УТ 519/519, tier-1 УТ 1282/1283,
справочники acc+erp+ut 2157/2159, сюиты meta-compile 76/76 и
meta-decompile 3/3. Два расхождения в справочниках — давний пробел по
предопределённым элементам, воспроизводится и на коде до правки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прогон сервисов на конфигурациях формата 2.17 (ERP + БП, 46 объектов) вскрыл
то, чего не было в УТ: 25 совпадений из 46.
ERP двуязычна, и синоним шаблона, метода, операции и параметра приезжает
объектом {ru,en}. Компилятор интерполировал его в строку — в XML попадал
литерал "@{ru=Версия; en=Version}" вместо пары языковых элементов. Значение
теперь передаётся в эмиттер как есть, он и так умеет обе формы.
RootURL сравнивался с дефолтом (имя в нижнем регистре) регистронезависимо,
поэтому MobileAppReceiptScanner считался дефолтным и терял регистр при
регенерации. Сравнение сделано регистрочувствительным — тот же класс ошибки,
что уже ловили на синонимах методов.
Спека meta-dsl-spec дополнена: объектные формы шаблонов, методов, операций и
параметров; xdtoPackages как список; descriptorFileName; dataLockControlMode;
нотация Кларка для типов из своего пространства имён; Use в ReuseSessions.
Проверка: сервисы ERP+БП 46/46, сервисы УТ 25/25, сюита 76/76 на обоих
портах, 1С-сертификация кейса с многоязычным синонимом пройдена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
meta-decompile не знал тип WebService — 18 сервисов УТ не проходили раундтрип.
Добавлен разбор (оба порта): namespace, состав XDTO-пакетов, дескриптор,
операции с параметрами.
Компилятор поддерживал тип поверхностно; раундтрип на реальных сервисах
показал, чего не хватало:
- XDTOPackages эмитился скаляром, а это СПИСОК элементов: ссылка на пакет
конфигурации (xr:MDObjectRef) либо URI внешнего пространства имён
(xs:string). Presentation пуст, CheckState 0 — 19/19 по корпусу;
- DescriptorFileName не эмитился вовсе, хотя есть у всех 18 сервисов и НЕ
выводится из имени (DMILService -> dmil.1cws) — нужен явный ключ;
- DataLockControlMode не эмитился (Managed у всех 192 операций);
- Comment не эмитился ни у сервиса, ни у операций и параметров;
- типы из собственного пространства имён (81 случай) писались без локального
xmlns. В DSL задаются нотацией Кларка "{uri}ИмяТипа", компилятор объявляет
xmlns сам — как это делает платформа.
Nillable захватывается явно у операций и параметров: по корпусу значения
смешанные (операции 103/89, параметры 128/395), дефолт угадать нельзя.
Порядок операций и параметров приведён к порядку DSL в обоих портах — PS шёл
в порядке хеш-таблицы, py сортировал.
Проверка: 25 сервисов УТ (18 WebService + 7 HTTPService) 25/25 без
расхождений; JSON декомпилятора побайтово совпадает у PS и py на всех 25;
сюита 76/76 на обоих портах; 1С-сертификация обоих кейсов пройдена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
meta-decompile не знал тип HTTPService, поэтому 7 сервисов УТ не проходили
раундтрип вовсе. Добавлен разбор: rootURL, reuseSessions, sessionMaxAge и
дерево urlTemplates -> methods.
Раундтрип на реальных сервисах вскрыл три пробела компилятора:
1. ReuseSessions: в allowlist были только DontUse и AutoUse, а платформа
пишет ещё и Use (4 объекта в корпусе) — компиляция падала с ошибкой.
2. Обработчик метода выводился по формуле ИмяШаблона+ИмяМетода; в реальных
конфигурациях он произвольный (УдаленныйВызовМетодаЧерезТелоЗапроса).
Метод получил объектную форму {httpMethod, handler, synonym, comment}
рядом со строчным сокращением "только HTTP-метод".
3. Comment не эмитился ни у объекта, ни у шаблонов и методов, хотя платформа
пишет тег всегда.
Порядок шаблонов и методов приведён к порядку DSL в обоих портах: PS шёл в
порядке хеш-таблицы, py сортировал — снэпшоты бы разъехались между портами.
В декомпиляторе сравнение синонима с авто-выводом сделано регистрочувствительным
(-ceq): "Post" против "post" считалось совпадением, и синоним терялся.
Проверка: HTTP-сервисы УТ 7/7 без расхождений (было 0 — тип не поддерживался),
сюита 76/76 на обоих портах, 1С-сертификация кейса пройдена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Раундтрип по обработкам и отчётам УТ (7568 объектов) показал 265 лишних
строк: мы эмитили LineNumberLength каждой табличной части, а платформа
пишет его только объектам, у которых есть таблица в базе.
Проверено на двух конфигурациях независимо: у обработок 0 тегов из 235+257
табличных частей, у отчётов 0 из 106+8; у справочников, документов, ПВХ,
планов обмена и бизнес-процессов тег стоит поголовно. Свойство задаёт
разрядность физического номера строки в таблице БД — у обработок и отчётов
таблиц нет, хранить нечего.
Исключены DataProcessor и Report, а также ExternalDataProcessor и
ExternalReport: внешние обработки той же природы, и при сборке EPF на
формате 2.20 мы бы писали тег, которого платформа не пишет.
Кейс format-220-props дополнен обработкой с табличной частью: у неё тега
нет, у справочника в той же конфигурации LineNumberLength=9.
Проверка: УТ tier-2 7543/7543 без расхождений (было 71 diff),
сюита 76/76 на обоих портах, 1С-сертификация на 8.3.27 пройдена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Значением параметра выбора и значения заполнения бывает ЗНАЧЕНИЕ — пустая
ссылка, предопределённый элемент или значение перечисления. Объект метаданных
значением быть не может, поэтому двухчастное "Тип.Имя" — это строка.
Правило уже существовало, но только для перечислений ("Enum.X" не значение);
для остальных корней его не было, и строка "Документ.РеализацияТоваровУслуг"
превращалась в xr:DesignTimeRef с переводом имени на английский — молчаливая
подмена смысла: вместо текста получалась ссылка на реальный документ.
Проверка по корпусу (acc+erp+ut): 5520 значений в ChoiceParameters и 8623 в
FillValue — двухчастных с типом метаданных НЕТ ни одного. Прощающий ввод не
пострадал: "Справочник.Номенклатура.ПустаяСсылка" -> Catalog.Номенклатура.EmptyRef,
"Перечисление.X.Y" -> Enum.X.EnumValue.Y работают как раньше (закреплено кейсом).
Проверка: УТ tier-1 1283 -> 1282 совпадения, справочники 519/519,
сюита 76/76 на обоих портах, 1С-сертификация кейса пройдена.
Остаётся 1 расхождение — план обмена без Ext/Content.xml (хвостовая аномалия
конфигурации: на одной БП все четыре платформы файл пишут).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Раундтрип по документам и регистрам УТ (1283 объекта) вскрыл два пробела.
TypeReductionMode измерения РС: декомпилятор значение захватывал, но парсер
измерения в компиляторе не переносил ключ дальше, и любое отклонение от
дефолта молча заменялось на TransformValues. Так терялось третье значение
свойства — DeleteData («Удалять данные»); всего у свойства три режима:
Преобразовывать значения (дефолт), Удалять данные, Запрещать.
Синтетика на матрице платформ: DeleteData принимается и переживает роундтрип
на 8.3.25, 8.3.26 и 8.3.27 — порог тот же, что у самого свойства (2.18).
nil-элемент внутри FixedArray (<v8:Value xsi:nil="true"/>) декомпилировался
пустой строкой, компилятор эмитил xs:string. Теперь это JSON null в обе
стороны. Конструкция не новая — есть уже в дампе 8.3.20.
Спеки: у TypeReductionMode перечислены все три значения с названиями из
конфигуратора; версия появления исправлена с 2.20 на 2.18.
Проверка: УТ tier-1 1283 объекта -> 1280 совпадений (было 1273),
сюита meta-compile 76/76 на обоих портах.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Раундтрип по справочникам УТ 11.5.27 вскрыл: <app:value xsi:type="xr:DesignTimeRef"/>
с пустым содержимым декомпилировался в "value": "", и компилятор законно
эмитил свой дефолт xs:string — тип ссылки терялся.
Конвенция для этого случая уже была (маркер {emptyRef: true} у fillValue),
пробел был только в ChoiceParameters. Декомпилятор теперь ставит маркер,
компилятор его понимает — и в скалярном значении, и внутри FixedArray.
Форма редкая, поэтому прежние кампании её не поймали: в acc она встречается
1 раз, в erp — ни разу, в УТ — 7 раз.
Is-EmptyRef в PS явно отсекает коллекции: у массива $v.emptyRef разворачивается
в свойства элементов (member enumeration), и массив с одним таким элементом
схлопывал FixedArray в скаляр. В py-порте isinstance(v, dict) такого не допускает —
расхождение поймано снэпшотом.
Проверка: справочники УТ 519/519 без расхождений (было 519 diff),
сюита meta-compile 76/76 на обоих портах, 1С-сертификация кейса пройдена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Замер на реквизите CatalogRef (8.3.24): в базе с подходящей конфигурацией
конфигуратор и ibcmd дают одинаковый результат — имя типа
(cfg:CatalogRef.Валюты). Движок начинает влиять, только когда база не та:
ibcmd оставляет идентификатор типа (<v8:TypeId>), 1cv8 подменяет тип на
xs:string с квалификаторами строки.
Годится только имя типа: оно резолвится по имени и собирается в любой
конфигурации с одноимённым объектом. Идентификатор привязан к исходной
конфигурации — в чужой базе сборка проходит без ошибки, но uuid остаётся
висячим. Оба деградированных варианта молчаливы, поэтому разбирать EPF/ERF
нужно только в базе с подходящей конфигурацией.
Инструкции навыков epf-dump/erf-dump намеренно не меняются: ограничение там
уже сформулировано, а объяснение «что будет, если разобрать в пустой базе»
только провоцирует так делать.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Спеки утверждали, что за 2.17 (8.3.20-8.3.24) сразу идёт 2.20 (8.3.27+),
и приписывали версии 2.20 всю дельту. Промежуточные версии существуют:
8.3.25 пишет 2.18, 8.3.26 - 2.19.
Атрибуция изменений исправлена по замеру одной конфигурации, выгруженной
четырьмя платформами: TypeReductionMode и TextToSpeech - 2.18, отказ ролей
писать право, равное setForNewObjects, - 2.19, LineNumberLength - 2.20.
Два утверждения о «дельте 2.20» в 1c-configuration-spec § 7 были ошибочны и
удалены. Сжатая шапка xmlns оказалась артефактом стороннего сериализатора в
эталонном дампе, а не форматом: платформа во всех версиях пишет полную шапку.
Стиль пустых элементов признаком версии не является - он различается и между
дампами одной версии.
Добавлено: ConfigurationExtensionCompatibilityMode платформа при выгрузке
подставляет свой собственный даже при неизменной конфигурации; три свойства
форм из 2.18 - изменение дефолта эмиссии, а не новые свойства (8.3.24
принимает их и сохраняет при роундтрипе, в отличие от TypeReductionMode).
В db-guide зафиксировано побайтовое совпадение выгрузок конфигуратором и
ibcmd - с оговоркой, что на разбор EPF/ERF это не переносится: там результат
определяется базой разбора, а не движком.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Версия формата выгрузки идёт лестницей, а не скачком 2.17 -> 2.20:
8.3.20-8.3.24 -> 2.17, 8.3.25 -> 2.18, 8.3.26 -> 2.19, 8.3.27 -> 2.20.
Прежняя дельта мерилась через две ступени и приписала версии 2.20 всё,
что появилось по дороге.
Замер на одной и той же конфигурации, выгруженной четырьмя платформами:
- 2.18: TypeReductionMode (4793 файла), TextToSpeech, 3 редких свойства форм;
- 2.19: новых тегов нет, роли перешли на omit-on-default;
- 2.20: только LineNumberLength.
meta-compile: единый гейт isFormat220 управлял обоими свойствами, поэтому на
проекте 2.18/2.19 TypeReductionMode не эмитился, хотя платформа этих версий
его пишет — роундтрип разъезжался. Порог расщеплён: >=2.18 для
TypeReductionMode, >=2.20 для LineNumberLength.
meta-validate: реестр versionedProps объявлял TypeReductionMode свойством
2.20, из-за чего проверка 18 давала ложную ошибку на корректном файле
2.18/2.19. Исправлено на 2.18.
Валидаторы (meta/form/cf/cfe/epf) считали 2.18 и 2.19 неизвестными версиями
и предупреждали «Unusual version»; cf-init не давал отскаффолдить
конфигурацию под 8.3.25/8.3.26. Обе версии впущены.
Тесты: фикстура empty-config-218, кейс meta-compile на границу свойств
(TypeReductionMode есть, LineNumberLength нет), два кейса meta-validate на
законность штампов 2.18/2.19; кейс error-220-props-in-217 теперь проверяет
оба сообщения с разными порогами. Кейс 2.18 проверен загрузкой в живую
8.3.25 через verify-snapshots.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ограничение «только файловые базы» — свойство обвязки навыков, а не утилиты:
ibcmd умеет клиент-серверные базы, но подключается к СУБД напрямую и требует
реквизитов, которых навыки не запрашивают. Плюс добавлен headless-сценарий для
Linux/macOS с примером v8path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Хвосты после разбора: db-run и stub-db-create остались единственными,
кто не прощал обрамляющие кавычки и хвостовой разделитель в путях.
Плюс db-create правился в коммите паритета без бампа версии, а раннер —
без бампа своего заголовка.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кейсы, разведённые по портам из-за расхождения вывода, слиты обратно —
это и есть приёмочный признак паритета. Цепочка epf-build разделена на
два кейса: успешный запуск временной базы теперь молчит, поэтому её
командная строка проверяется на падающем фейке.
Новое: чтение вывода платформы в UTF-8 и cp866, отсутствие блока при
молчащей платформе, форма токена для пути с пробелом, отказ до запуска
при отсутствующей базе.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Порты расходились в трёх местах, и каждое проявлялось только в редком
случае — то есть там, где цена ошибки максимальна.
1. Вывод. PS наследовал консоль (текст платформы попадал в поток без
метки и в непредсказуемой позиции), PY захватывал его и не печатал
вовсе — аварийное сообщение мимо /Out терялось. Теперь оба порта
захватывают вывод и печатают его отдельным блоком «Вывод платформы»,
только если он непуст: молчащий успех остаётся молчаливым.
2. Кодировка. PS декодировал вывод ibcmd как cp866, тогда как ibcmd
пишет UTF-8 (проверено на 8.3.24, 8.3.27, 8.5) — русские сообщения
приходили крякозябрами. PY использовал text=True, то есть локальную
кодовую страницу. Теперь оба декодируют UTF-8 строго, с фолбэком на
cp866 для аварийного текста 1cv8.
3. Квотирование. PY не работал с путём к базе, содержащим пробел, — ни
на Windows, ни на macOS: 1С ждёт кавычки внутри значения
(File="путь"), а subprocess квотирует токен целиком. PS работал,
потому что вклеивал кавычки сам. Теперь обе версии строят токены
одинаково; на Windows PY передаёт готовую командную строку.
Плюс валидация ввода: путевые параметры прощают обрамляющие кавычки,
пробелы по краям и хвостовой разделитель, а навыки, требующие готовую
базу, проверяют её наличие до запуска.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Фейк платформы, написанный как .cmd, на macOS не исполняется ни одним
портом — такие кейсы падали на маке вместо пропуска. runtimeOnly для
этого не годится: ограничение не по порту, а по ОС.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Список пишется одной строкой через запятую — так же, как -Objects/-Files;
отмечено ограничение: значение с запятой внутри не поддерживается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
expect.stdoutContains/stdoutNotContains жили в ветке успеха, поэтому у
кейса с expectError проверялся только ненулевой код возврата, а строки
не смотрелись вовсе. Пятнадцать кейсов (включая xdto-validate,
meta-validate, form-validate, meta-remove) были зелёными вхолостую —
у facet-conflicts текст навыка успел разойтись с ожиданием.
Плюс case-level "cwd": "workDir" — кейсу может понадобиться, чтобы
навык стартовал внутри рабочего каталога (фикстура .v8-project.json).
Кейсы на доп. аргументы платформы: pass-through 1cv8 и ibcmd, источник
из реестра проекта, цепочка epf-build → stub, конфликт ключа,
позиционный токен ibcmd, чужой движок, маскирование секрета.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
powershell.exe -File (именно так вызываются навыки) не умеет биндить
массив: значения через пробел уходят в позиционные параметры, а список
через запятую приезжает одним склеенным токеном. Поэтому параметр
принимает список в конвенции репозитория (как -Objects/-Files) и
разбирается внутри; нативный вызов массивом продолжает работать.
То же разбиение добавлено в py-порт — чтобы одна и та же строка вызова
работала в обоих. Значение с запятой внутри не поддерживается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Разбор escape hatch для вызывающего: два параметра по движкам, ключи
v8args/ibcmdargs в реестре проекта, что отклоняется и почему,
маскирование секретов и известные пределы разбора (/Psecret, /@).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Набор аргументов платформы был закрыт: общий ключ запуска (например
/UseHwLicenses+ на машине с аппаратной лицензией) передать было нельзя,
и сборка на автоматически созданной временной базе падала с «Не найдена
лицензия».
Добавлен escape hatch — по параметру на движок, плюс зеркальные ключи
в .v8-project.json (v8args / ibcmdargs) для машинно-специфичных флагов:
- -AdditionalV8Arguments → 1cv8.exe, ключи вида /Key
- -AdditionalIbcmdArguments → ibcmd, ключи вида --key=value
Аргументы уходят во все запуски платформы, которые делает навык:
epf-build без базы прогоняет CREATEINFOBASE, /LoadConfigFromFiles,
/UpdateDBCfg и саму сборку — ключ получает каждый.
До запуска отклоняются: аргумент, которым управляет сам скрипт (режим,
подключение, /Out, пакетная операция), позиционный токен для ibcmd и
параметр «не своего» движка. Значения секрето-опасных ключей (/P, /UC,
--password, --token) в логе маскируются.
Порядок источников: .v8-project.json, затем параметр. Поведение без
новых параметров не меняется.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
На боевой форме вскрылись случаи, которые прежнее правило не покрывало. Замеры показали,
что любая эвристика «по первому сиблингу за заголовком» нежизнеспособна:
- служебная обёртка .logicGroupContainer пишется двумя способами (у таблицы
<дочерний>#group_div, у вложенной группы <дочерний>_div) — знали только первый;
- display самой обёртки плавает: block до первого тогла, none после;
- первые узлы группы могут быть скрыты своей логикой, а видимое содержимое идёт дальше
по цепочке — раскрытая группа читалась как свёрнутая, и постусловие роняло успешный клик.
Теперь состояние берётся из контрола сворачивания: у варианта «картинка» — кадр gx спрайта
hideshow у каретки (полярность обратна дереву в dom/grid.mjs), у варианта «гиперссылка»
каретки нет, там принадлежность по отступу — дети группы смещены глубже её заголовка,
свободный сосед стоит на уровне заголовка. База отсчёта — левый край блока заголовка
(каретка сдвигает текст вправо), обход ограничен: у последней свёрнутой группы границы за
ней нет, и первый видимый узел нашёлся через 107 сиблингов в чужой ветке формы.
Клик по заголовку группы теперь сперва скроллит цель в вид: цель кликается по координатам,
и ниже вьюпорта клик молча не доходил.
Ответ на клик отдаёт clicked.group (техническое имя) и clicked.title (текущий заголовок):
заголовок ключом быть не может — он повторяется между блоками формы и меняется при
раскрытии (CollapsedRepresentationTitle), после чего клик по прежнему тексту не находит
элемент. При смене заголовка hint говорит, чем кликать дальше.
Фикстура: вложенная группа первым ребёнком, «скрыт первый узел» в двух вариантах контрола,
группа с меняющимся заголовком в конце формы (уезжает за вьюпорт). Каждый кейс проверен на
красноту без своей правки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>