Ячейка получила третий параметр — pictureParameter, имя параметра, которым
подставляют картинку. Сама картинка задаётся оформлением (picIndex), а этот
тег живёт у ячейки, последним из её параметров, и уживается с текстом.
В корпусе таких ячеек 21 в 9 макетах; теперь все 21 возвращаются обратно.
Прозрачность картинки сведена к одному ключу transparent: false — фона нет,
{ x, y } — прозрачен цвет пикселя с этими координатами. Два способа записи
у платформы исключают друг друга (t принимает только false, включённую
прозрачность выражают tx/ty), так что двум ключам DSL соответствовал один
флажок диалога.
Заодно выровнен порядок ключей в проверке «объект описывает ячейку»: в
py-порте не хватало note.
Одна и та же запись `ref="v8ui:Имя"` стоит и за предопределённой картинкой
платформы, и за общей картинкой конфигурации — по макету источник не
определить. Загрузку это не ломает: платформа ссылку при загрузке не
проверяет, макет со ссылкой на отсутствующую общую картинку принимается.
Заодно снят прежний вывод «tx/ty с ref не встречаются»: на стенде такая
запись есть, координаты идут перед ссылкой. Раундтрип на ней сходится.
Атрибут прозрачности живёт не только у картинки с данными: платформа пишет
<picture t="false" ref="v8ui:Имя"/>, причём t перед ref. Компилятор в этой
ветке его терял, декомпилятор не читал — в «Бухгалтерии предприятия» на
8.3.27 таких записей 67.
Заодно уточнена картина по конфигурациям: t="false" стоит у 11 135 картинок
БП, значения true не бывает нигде — включённую прозрачность платформа
выражает координатами пикселя, и вместе с t они не встречаются.
Одна и та же картинка 25×30 вставлена в стенд дважды: без флажка
«прозрачный фон» платформа пишет t="false", с флажком — tx="24" ty="29",
то есть координату правого нижнего пикселя. Два способа записи исключают
друг друга. Стенд с обоими случаями положен в фикстуры.
Рисунок поверх сетки — картинка, фигура, надпись — теперь описывается в DSL
и возвращается из макета: два якоря «ячейка + смещение», тип, ссылка на
картинку, надпись, расшифровка, имя, порядок перекрытия.
Оформление рисунка разложено надвое: общее (заливка, шрифт, выравнивание)
берётся из именованного стиля, а линия и её стороны — собственные ключи
рисунка. У ячейки таких свойств не бывает: все 2 135 записей палитры с ними
принадлежат рисункам.
Палитра картинок: ссылка на библиотеку платформы, данные base64, пустая
запись «картинка не задана» (151 в корпусе) и координата пикселя прозрачного
цвета (tx/ty — проверено, это не размеры картинки).
Заодно: висячий пустой колонтитул в ps1-порте (пустой словарь в PowerShell
истинен, порты расходились на 3 макетах из 40) и проверка индекса формата
рисунка в mxl-validate.
Стенды Рисунки, Рисунки2, КартинкаВЯчейке проходят раундтрип байт в байт
в обоих портах; на 35 корпусных макетах с рисунками расхождение сократилось
у всех 35.
Оба списка снимались с корпуса ERP и потому отвергали законные значения.
Узоров платформа знает семнадцать («Узор 1» … «Узор 17»), а в корпусе
встречаются только шесть номеров — Pattern4 компилятор отклонял.
Со стилями линии тоньше: палитра одна на документ, но Конфигуратор
предлагает разные наборы в разных местах. У рамки ячейки семь значений
(корпус их покрывал), у линии рисунка — шесть других, и трёх из них
(Dashed, DashDotted, DashDottedDotted) в корпусе нет ни одной. Домен —
объединение обоих наборов.
Обе поправки сняты с диалогов Конфигуратора, а не додуманы по образцу.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Именованная область ссылается на дополнительный набор колонок тегом
columnsID, и эта ссылка терялась целиком: на пилоте кампании она давала
3572 расхождения из 3572 в своей категории, на корпусе таких областей
913 483 прямоугольных, 20 063 полосы строк и 743 полосы колонок.
Ключ namedAreas[].columnSet имеет три состояния: ключа нет — привязка
выводится из накрытых строк (совпадает у 1 021 570 областей корпуса из
1 044 339), "" — привязки нет вовсе, имя набора — явная привязка.
Переопределение обязательно: у областей типа Rows 13 623 повторяют
раскладку строк, а 8 179 её не несут при тех же строках.
Декомпилятор пишет ключ, только когда он не выводится, а область, чья
привязка расходится с раскладкой её строк, уводит из блочной формы в
namedAreas — иначе блоком её не выразить.
Пустая строка тоже попадает в карту «строка → раскладка»: без этого
привязка области, накрывающей пустые строки, не выводилась.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ключи header и footer описывают колонтитул: три слота (left, center,
right), общий шрифт и вертикальное выравнивание, признак вывода и
страница, с которой печатать. Ключ printSettings — плоский набор
параметров печати с именами тегов платформы; порядок компилятор
выставляет сам, незнакомый ключ отвергает.
Слот устроен как ячейка: ссылка на формат плюс текст. Текст бывает двух
видов, и это не текст против шаблона, а обычная строка против
форматированной: у форматированной разметка живёт прямо в содержимом
(<b>жирный</>, <fontsize 12>, <colorstyle -16>), поэтому она возится как
есть. Признак вывода и стартовая страница лежат в формате как <height>
и <width>, но читаются только у записи с <height>: палитра
дедуплицирована, и колонтитул без своих настроек ссылается на чужую
запись, где <width> — ширина колонки (637 таких ссылок против 153 своих).
Перенос строки внутри текста колонтитула платформа хранит голым LF при
CRLF во всём остальном файле — единственное такое место в документе,
поэтому содержимое пишется одной строкой вывода.
Формат колонтитула регистрируется до отсева неиспользуемых шрифтов:
иначе шрифт, на который ссылается только колонтитул, выбрасывался бы
из палитры.
Стенд Колонтитулы собирается байт в байт обоими портами и добавлен
в платформенные фикстуры.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Документные ключи rowGroups и columnGroups описывают сворачиваемые
группы: диапазон, имя, признак свёрнутости и расположение заголовка.
Форма плоская, как в формате: вложенность выражена вхождением одного
диапазона в другой. Дерево было бы читаемее, но платформа хранит плоско,
и для правки чужого макета дерево дороже — чтобы добавить группу внутрь
существующей, пришлось бы искать родителя. Частичных пересечений
платформа не порождает (корпус: 40 620 886 пар непересекающихся,
599 958 вложенных, ни одного частичного), поэтому компилятор их
отвергает.
Число уровней вложенности не задаётся: оно совпало с <vgLevels> у всех
1797 макетов корпуса. Порядок записи компилятор приводит к
платформенному — родитель раньше вложенных, по возрастанию начала
(совпало у 1798 макетов из 1798).
Имя группы вывести нельзя, хотя 143 455 имён корпуса выглядят как
автоматические R<номер строки>: 16 666 из них протухли после вставки
строк, а 10 287 групп имени не имеют вовсе. Расположение заголовка взяло
ключ titleLocation — то же свойство и то же имя, что в DSL форм.
mxl-validate: проверка 14 — перевёрнутый диапазон, частичное пересечение,
начало группы за пределами документа.
Стенд Группировки собирается байт в байт обоими портами и добавлен
в платформенные фикстуры.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ключ ячейки note описывает всплывающую подсказку: строкой, объектом
языков или полной формой с оформлением, признаком авторазмера и
геометрией окошка.
Из четырнадцати тегов, которые платформа пишет в примечание, настоящей
информации несут пять. drawingType, pictureSize и id — константы на всём
корпусе. Якорь конца это координаты самой ячейки (1087 примечаний из
1087), якорь начала — 1/1 (1085 из 1087, три исключения в одном макете
берёт раундтрип-ключ anchor). Остаётся текст, стиль, autoSize и четыре
смещения, причём autoSize описывает не наличие геометрии, а пересчёт
размера: при true координаты всё равно записаны и осмысленны.
Стиль примечания — обычная запись палитры; без своего стиля пишем тот,
что даёт Конфигуратор (926 примечаний корпуса из 1087). Формат
примечания добавлен в сбор именованных стилей декомпилятора и в перечень
владельцев формата: иначе стиль подсказки вырезался как неиспользуемый и
ссылка оставалась висячей.
mxl-validate: индекс формата примечания проверяется наравне с ячейкой,
строкой и колонкой.
Стенд СПримечанием собирается байт в байт обоими портами и добавлен в
платформенные фикстуры.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В перечне свойств стиля осталось прежнее имя ключа: свойства значения
задаются через valueType и controlType, а не control.
Каскад навыка почищен от того, что нужно раундтрипу, а не автору: ключ
настроек элемента управления и значение "none" остались только в
спецификации, мотивировка «почему значение не приводится к объявленному
типу» заменена самим правилом. Из SKILL.md убрано число свойств стиля —
оно разъезжается с перечнем при каждом пополнении.
Зеркало reference/dsl-spec.md с этого момента не копия docs/: в
спецификации подробности, в инструкции навыка — применение.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ключ control возился дословно, но проверялся только косвенно — через
платформенную фикстуру стенда. Теперь у него свои кейсы на обе стороны
(сборка блоба из DSL и раундтрип), строка в описании ячейки с пометкой
«раундтрип, не для ручного авторинга» и проверка в валидаторе: значение
и настройки элемента управления бывают только у ячейки-поля ввода —
на корпусе ни одного вхождения в обычной ячейке.
Проверки ячеек со значением заодно перестали зависеть от наличия таких
форматов в макете: раньше блок целиком пропускался, когда ни одного
формата с признаком значения нет, — то есть ровно в том случае, который
эта проверка и ловит.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ключ ячейки value несёт значение, набранное в поле ввода, ключ control —
сериализованные настройки самого элемента управления. Оба живут у ячейки,
а не в палитре формата: у двух ячеек с одинаковым оформлением значения
разные.
Тип значения выражается литералом JSON и приведения к объявленному типу
НЕ делается. Так пишет платформа: ссылочный и составной тип она хранит
строкой, а при смене типа ячейки прежнее значение не переписывает — на
корпусе 560 ячеек объявлены числом, но несут строку. Приведение здесь
означало бы переписывать данные при пересборке. Дата литералом JSON не
выражается, поэтому едет строкой и читается как дата только у ячейки,
объявленной датой.
Настройки элемента управления возим дословно: 26 370 вхождений корпуса
дают всего 163 различных блоба, разбирать структуру незачем. Переводы
строк внутри блоба приводим к LF на чтении — XML-парсеры двух портов
нормализуют их по-разному, и без этого порты давали разный JSON на одном
файле.
Стенд СЗначениями снова собирается байт в байт обоими портами: фикстура
обновлена до версии со значениями, флажком и блобом настроек.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ключ detail описывал ячейку только вместе с param, а платформа ставит
расшифровку самостоятельно: на корпусе ERP 20 404 ячейки несут её БЕЗ
параметра (12 653 пустых, 5 949 с текстом, 1 802 поля ввода) против
8 582 с параметром. Компилятор такую расшифровку молча выбрасывал,
декомпилятор молча не читал, а ячейку, где кроме расшифровки ничего нет,
считал заполнителем строки и терял целиком.
Заодно выправлен порядок тегов ячейки: расшифровка идёт ПОСЛЕ текста,
а не перед ним (корпус: f · parameter · tl|v · detailParameter).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Конфигуратор предлагает флажок только для Булево и Числа, но ограничение
интерфейсное: макет с флажком у строки и у даты платформа принимает и
возвращает GUID дословно — проверено сборкой EPF и обратной выгрузкой
через базу. Поэтому предупреждение, а не ошибка компиляции: собрать
такую ячейку вручную нельзя, и почти наверняка это описка автора.
На корпусе ERP предупреждение не срабатывает ни разу: все семь флажков
стоят у булева.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Тег ячейки <control> и тег формата <controlType> в выгрузке — разные
вещи: первый несёт сериализованные настройки элемента управления, второй
GUID его вида. Ключ DSL описывал второй, а назывался как первый, и при
поддержке настроек отображение стало бы перекрёстным.
Свести их в один ключ нельзя: <controlType> живёт в палитре и разделяется
ячейками с одинаковым оформлением, а настройки принадлежат конкретной
ячейке. Синоним не заводим — он занял бы ровно то имя, которое
освобождается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Тип значения ячейки и элемент управления переживают цикл компиляции и
декомпиляции. Макет стенда с полями ввода собирается из своего же JSON
байт в байт.
DSL: два ключа ячейки — valueType (грамматика типа семьи: примитивы с
квалификаторами, ссылочные типы, категории целиком, составной через " + ")
и control (input/checkbox, синонимы, GUID для неизвестных элементов, none
для формата вовсе без тега). containsValue отдельным ключом не выражается:
он выводится из наличия типа. Текст и шаблон в такой ячейке запрещены,
параметр и расшифровка допустимы.
Умолчания голых типов взяты платформенные (строка без длины безлимитна,
число без параметров без ограничения разрядности), поэтому эмиттер типа
свой, а не копия meta-compile; resolve_type_str скопирован из эталона
семьи и объявлен в реестре дрейфа. Канон типа в ключе дедупликации
палитры развёрнут: иначе разные написания одного типа дали бы две
одинаковые записи формата и сдвинули бы все ссылки ячеек.
mxl-validate: проверка согласованности таких форматов — тип без признака
значения, ячейка с текстом и значением одновременно, посторонний тег
внутри типа, формат значения у строки или колонки, неизвестный GUID
элемента управления.
Попутно сведены два расхождения портов mxl-validate: py молчал там, где
ps1 писал строку об отсутствии шрифтов, и шапка подробного вывода
печаталась в разной форме.
Версии сведены: mxl-compile 1.41 в обоих портах (было 1.40/1.39).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сплошной прогон по типам объектов (11 минимальных объектных форм УТ, каждая
заимствована и загружена в UT_DEMO) дал три падения из одиннадцати. Все три —
один класс: в оболочке не хватает того, от чего зависит существование
стандартного поля, и платформа отвергает загрузку «Неверный путь к данным».
1. Справочник с владельцем: «Объект.Owner» не разрешается без <Owners>.
Свойство — список <xr:Item>, а не скаляр, поэтому переносится фрагментом,
как __TypeXml у DefinedType. Одного переноса мало: ссылка должна вести на
объект, который в расширении есть, иначе платформа падает с access violation
вместо сообщения. Добавлен общий проход, заимствующий владельцев (и владельцев
владельцев) — Конфигуратор поступает так же, эталон Issue66Example7_1.
2. Регистр сведений: «Запись.Period» не разрешается без
InformationRegisterPeriodicity. Вместе с ним переносится WriteMode, от
которого зависит «Запись.Recorder» — Конфигуратор несёт оба, 6 эталонов из 6.
3. Задача: «Объект.Исполнитель» — это реквизит адресации, отдельный вид
дочернего объекта. AddressingAttribute добавлен к видам, которые
заимствуются поимённо, рядом с Dimension и Resource.
Правка сделана в Read-SourceObject и Build-BorrowedObjectXml — там, где оболочка
рождается: механизм $extraProps работает только в потоке -BorrowMainAttribute и
обычное заимствование оболочки не покрывает.
Проверено: те же 11 форм грузятся 11 из 11; ПВХ с путём «Объект.ValueType»
грузился и раньше — Конфигуратор Type и CharacteristicExtValues тоже не
переносит (эталон Issue66Example8), правило «свойство, включающее поле»
подтверждается с обеих сторон.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Эталоны Issue66Example7 и 7_1 (три формы, оба режима) показали, что прошлая
правка была неполной в двух местах.
1. Путь на СТАНДАРТНОЕ поле объекта («Объект.Owner», «Объект.Date», «Объект.Ref»)
текстом не разрешается даже при заимствованном основном реквизите: платформа
отвергает загрузку «Неверный путь к данным». Мы такой путь оставляли текстом.
Конфигуратор оставляет ссылку на сам реквизит — «1». Теперь так же: реквизит
объекта (есть в ChildObjects) остаётся читаемым текстом, стандартное поле
сводится к id основного реквизита. Проверено загрузкой: было падение, стало
успешно; выход совпал с эталоном 7_1 дословно.
2. Пути «Items.<Элемент>.CurrentData.<Поле>» при заимствованном основном
реквизите разрешаются текстом — элементы формы на месте, а их данные доступны
через основной реквизит. Конфигуратор их и не трогает. Прошлая правка вырезала
их в обоих режимах, то есть теряла рабочие связи. Теперь вырезаются только без
заимствования основного реквизита, где они действительно не разрешаются.
Уточнение по кодировке для будущей работы: без заимствования Конфигуратор
кодирует стандартное поле как «1/-N» (Owner у справочника и Ref у документа
дали -5, Date у документа -3), а Items-путь — как
«<id элемента>:02023637-7868-4a5f-8576-835a76e0c9ba/0:<uuid поля>». Таблицу
отрицательных индексов по трём точкам не построить, поэтому в скелетном режиме
обе разновидности по-прежнему вырезаются с предупреждением.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прогон verify-snapshots вскрыл, что два новых кейса опирались на вход, который
платформа не принимает — падала конфигурация-источник, а не расширение.
form-choice-link-items: путь «Items.Ячейка.CurrentData.Склад» был невалиден,
потому что Ячейка — поле ввода, а не таблица. Кейс пересобран на настоящей
табличной части: путь корректен, связь по-прежнему вырезается, фикстура
грузится. Кейс продолжает падать на коде до правки.
form-choice-link-dangling: вход намеренно содержит путь на несуществующий
реквизит объекта — в валидной конфигурации такой формы не бывает, а проверяется
именно поведение навыка на таком пути. Помечен skipPlatformVerify с причиной.
После правок verify-snapshots --skill cfe-borrow: 21 прошло, 0 упало,
1 пропущен по объявленной причине. cfe-validate — 7 из 7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Третий и последний класс дефектов из ишьи #66, единственный, который валит
загрузку. Rewrite-ChoiceParameterLinks переписывал только пути от «Объект.»;
ссылка с корнем-реквизитом ФОРМЫ проходила текстом, а реквизиты формы не
заимствуются никогда — путь не разрешался:
Неверный путь к полю - Помещение
Неверный путь к полю - Склад
Неверный путь к полю - ОтборСклад
Связь с постскриптумом репортёра прямая: в эталоне JR2976 ссылка
«ИспользоватьСоглашенияСКлиентами» — то самое имя из его сообщения —
закодирована как «51». В корпусе УТ таких форм 103, путей 424 из 1015.
Правило подтверждено на шести расширениях от Конфигуратора (Issue66Example4/5/6,
JR2433, JR2976, JR49904): подставляется id реквизита ИСХОДНОЙ формы. Именно
исходной — Issue66Example6 показывает, что даже с заимствованными реквизитами
формы (id 1000002/1000003) ссылки продолжают указывать на 4 и 5, то есть в
нумерацию базовой формы. Переписывать нужно в обоих режимах: реквизиты формы не
заимствуются ни при Form, ни при All, поэтому гейт «только без основного
реквизита» снят.
Путь «Объект.X» при заимствованном основном реквизите оставлен текстом:
он разрешается (проверено загрузкой и UpdateDBCfg) и читается. Конфигуратор
нормализует и его, но копировать обфускацию там, где она не нужна, незачем.
Зашитая единица в «1/0:<uuid>» заменена на реальный id основного реквизита
исходной формы.
Путь, который не сводится ни к одному известному виду, теперь вырезается с
предупреждением, а не остаётся текстом. Сюда попадают
«Items.<Элемент>.CurrentData.<Поле>» (303 в УТ): их кодировка непрозрачна и по
имеющимся эталонам не воспроизводима. Проверено — с таким путём форма не
грузилась вовсе; без связи заимствуется и работает. Связь параметров выбора —
удобство подбора, а не данные.
Проверено на UT_DEMO: расширение с обеими формами грузится и применяется к БД,
значения ссылок совпадают с эталонами (4, 5, 2) в обоих режимах и обоих портах.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Расшивание корня путей (8366dd49) включило проверку 14 на формах списка, и она
дала ложные ошибки на реальных формах: 'Список.DefaultPicture' и
'Список.ПредставлениеСостояния' объявлены висячими, хотя форма валидна.
У динамического списка набор полей — результат его запроса, а не состав
объекта: туда входят и стандартные поля списка, и псевдонимы запроса. Сверять
такие пути с ChildObjects нельзя в принципе. Корпусная проверка: на 1094 формах
списка УТ таких сегментов 3383 — DefaultPicture (1009), Ref (551), Date (341),
Number (331), Description (311) и псевдонимы вроде ПредставлениеСостояния.
Проверка 14 теперь пропускает формы, у которых основной реквизит —
динамический список. Проверка 12 и тексты сообщений параметризацию сохраняют:
там корень берётся из атрибута table=, и он совпадает с основным реквизитом в
339 случаях из 389, а в остальных не совпадал и до правки.
Кейс на висячий путь формы списка снят — его посылка была неверной; вместо него
кейс на отсутствие ложных ошибок, он падает при снятии гарда.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Проверки 12 (<AdditionalColumns table="...">) и 14 (пути против конфигурации-
источника) искали пути регуляркой, зашитой на корень «Объект». Он такой только
у формы объекта: у формы списка «Список», у формы записи регистра «Запись».
На таких формах регулярка не совпадала, и обе проверки молча не срабатывали —
валидатор рапортовал «чисто» на форме с висячим путём.
Корень теперь берётся из основного реквизита: сначала из <Attributes> самой
формы, затем из <BaseForm>. Нет его нигде — путей с корнем не бывает, проверка
пропускается. Имя подставляется и в регулярки, и в тексты сообщений: раньше в
сообщении стояло «Объект.X» независимо от формы, что дезинформировало.
Вместе с этим набор имён-кандидатов проверки 14 расширен с Attribute/
TabularSection до Dimension/Resource. Без этого замена корня превратила бы
тихий пропуск в ложные ошибки на форме записи регистра, где дочерние объекты —
измерения и ресурсы; отдельный кейс это фиксирует.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Проверка 11d искала висячие пути регуляркой, зашитой на корень «Объект».
Он такой только у формы объекта: у формы списка корень «Список», у формы
записи регистра «Запись». На таких формах регулярка не совпадала, и проверка
молча не срабатывала — валидатор рапортовал «чисто» на форме, которую
платформа отвергает с «Неверный путь к полю».
Узел основного реквизита BaseForm проверка уже доставала строкой выше, но
использовала только как булев признак. Теперь из него берётся имя — и для
регулярки, и для текста сообщения. Если основного реквизита нет и в BaseForm,
корень неизвестен и поведение остаётся прежним.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Заимствование дочерних объектов в оболочку шло по трём источникам имён:
*DataPath, <Field> и <AdditionalColumns table=...>. У формы списка с ручным
запросом реквизиты упомянуты ещё и в тексте запроса, и они не заимствовались:
на эталоне Issue66Example2 навык брал 5 реквизитов и 0 табличных частей,
Конфигуратор — 17 и 4.
Правило выведено по эталонам и совпадает с ними в обе стороны: Конфигуратор
переносит то, на что ссылается форма, а у динамического списка с ручным
запросом текст запроса — такое же место ссылки, как DataPath. Имена, которые
встречаются в <QueryText> целым словом, — это 21 из 27 дочерних объектов, и
множество совпадает со взятым Конфигуратором точно.
Разбирать язык запросов не нужно: имена-кандидаты и так фильтруются по
реальному составу объекта, поэтому лишние слова из запроса безвредны.
Состав оболочки теперь совпадает с эталонами на всех четырёх случаях:
форма документа 47 реквизитов и 2 ТЧ, список с запросом 17 и 4, список без
запроса 3 и 0 (Issue66Example3 — там правило «только видимое на форме», и
поведение не меняется), форма записи регистра 3 измерения и 1 ресурс.
По корпусу (13796 форм УТ + ERP) списков с запросом 2584, без него 2411.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кейсов на эти сценарии не было ни одного, поэтому оба дефекта жили молча.
Фикстура main-attr-kinds: документ с табличной частью и тремя формами
(документа, списка с динамическим списком и <Settings>) плюс регистр
сведений с измерением и ресурсом и его форма записи.
Три кейса:
- форма списка с -BorrowMainAttribute: реквизит «Список» типа DynamicList
переносится вместе с <Settings>, пути «Список.*» не вырезаны, «Объект» и
придуманного <SavedData> в выходе нет;
- форма записи регистра: реквизит «Запись» нужного типа, а в оболочке
<Dimension>/<Resource>, а не <Attribute>;
- повторное заимствование по объекту с табличной частью: второй заход
добавляет недостающий реквизит, ТЧ не вложены друг в друга.
Все три падают на коде до правок и проходят после.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Реквизит синтезировался с зашитыми name="Объект", типом <Тип>Object и
SavedData=true, а сверху доклеивались «довески» исходной формы. Для формы
списка это давало реквизит «Объект» объектного типа с настройками
динамического списка внутри, и платформа отвергала файл: «Исключение XDTO
произошло при чтении файла». Навык при этом отрабатывал молча, exit 0.
По корпусу (8675 форм с основным реквизитом, УТ + ERP) объектных только 4074:
динамический список — 3847, менеджер записи — 546, xs:string — 108, набор
констант — 97. <Settings xsi:type="DynamicList"> встречается ровно у
динамических списков, 3847 из 3847.
Эталоны Конфигуратора дают единое правило для всех видов: реквизит копируется
из исходной формы дословно, меняется только id. Теперь так и делается, а
вычисление типа из таблицы GeneratedType уходит вместе с тихим сбоем
«нет категории Object → cfg:.Имя» для перечислений, констант и регистров.
Следом расшиты ещё три места, зашитые на литерал «Объект»:
- вырезание путей к данным — корень берётся из имени основного реквизита,
иначе у формы списка вырезало бы все пути «Список.*»; путь ровно на сам
реквизит («Список» у таблицы формы) тоже сохраняется;
- сбор путей для заимствования дочерних объектов — на необъектных формах не
находил ничего, и в оболочку не попадало ни одного реквизита;
- вид дочернего объекта: измерения и ресурсы регистра переносятся как
<Dimension>/<Resource>, а не все как <Attribute>.
Проверено на трёх эталонах (форма документа, форма списка, форма записи
регистра): основной реквизит, корни путей к данным и виды дочерних объектов
оболочки совпадают. Расширение с формой списка и формой записи грузится в
UT_DEMO и применяется к БД — до правки загрузка падала.
Снапшоты: <Type> у основного реквизита теперь многострочный, как у
Конфигуратора; синтез писал его в одну строку.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Заимствованное содержимое вставлялось текстом по ВСЕМ вхождениям
<ChildObjects>, а у объекта с табличными частями свой контейнер один, но
вхождений столько же, сколько ТЧ. При повторном заимствовании по тому же
объекту ps1 выдавал невалидный XML («mismatched tag»), py — валидный, но с
табличными частями внутри табличной части и дублем ТЧ.
Отдельно ветка «Replace empty ChildObjects» в ps1 переписывала КАЖДЫЙ блок
содержимым первого — теряла данные.
Свой <ChildObjects> закрывается в файле последним: объект в файле один,
вложенные ТЧ закрываются раньше. Правило вынесено в общую функцию каждого
порта, на неё переведены все места вставки.
Дедуп при повторном заимствовании брал имена регуляркой «первый
<ChildObjects> до первого </ChildObjects>» — у объекта с ТЧ этот отрезок
обрывается на закрытии первой ТЧ: захватывает имена её колонок и теряет всё,
что идёт после. Заменён разбором прямых детей своего контейнера. В py дедуп
дополнительно сканировал все <Name> во всём файле, включая имя самого объекта.
Проверено: два заимствования подряд по одному документу дают в обоих портах
валидный XML, 5 табличных частей на верхнем уровне, вложенности и дублей нет;
выходы портов отличаются только свежими uuid.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PS вставлял лишнюю строку с табуляцией перед первым <Attribute>, когда
дозаписывал реквизиты в заимствованную оболочку с самозакрытым
<ChildObjects/>: элемент раскрывался пробельным узлом в DOM, а последующая
текстовая вставка добавляла отступ ещё раз. У Конфигуратора в таких файлах
пустых строк нет ни одной, py-порт делал правильно.
PS сведён к тому же текстовому раскрытию, что и py. Расхождение портов на
Document.ВнутреннееПотребление (-BorrowMainAttribute Form) закрыто: выходы
отличаются только свежесгенерированными uuid.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В фикстурах cfe-borrow CommandSet не встречался ни разу, поэтому потеря
свойств не ловилась. Добавлена фикстура doc-form-commandset — форма документа
с реальным порядком секций (CommandSet, затем AutoTime/UsePostingMode/
RepostOnWrite), вложенным CommandSet у таблицы, RowPictureDataPath от Объект.
и CommandInterface. Форма собрана файлом, а не form-compile: тот выдаёт
свойства ДО CommandSet, то есть как раз не тот порядок, который нужен.
Два кейса над ней — заимствование без основного реквизита и с ним. Оба падают
на коде до правки и проходят после.
README: задокументированы ключи expect fileContains/fileNotContains/filesEqual,
которые раннер понимает, но в таблице их не было. Плюс предупреждение, что
подстрока сверяется буквально: ожидание "<CommandSet></CommandSet>" не
срабатывает на переводе строки внутри и потому не проверяет ничего.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Свойства уровня формы собирались до первой «визуальной» секции. В этот
список входил CommandSet, который в реальных формах стоит В СЕРЕДИНЕ блока
свойств: у всех 794 форм документов ERP с CommandSet он предшествует
AutoCommandBar, а AutoTime/UsePostingMode/RepostOnWrite идут после него.
Отсечка теряла весь хвост — в одном ERP это 2062 формы.
Отказа загрузки это не давало: платформа принимает форму и молча подставляет
дефолты. На Document.ЧекККМ.ФормаДокумента (УТ) AutoTime DontUse становился
CurrentOrLast, UsePostingMode Regular — Auto; исключённые стандартные команды
(Провести, Записать, Отменить проведение) возвращались в панель.
Граница теперь — AutoCommandBar: он есть в 100% из 30223 форм корпуса, и ни
одно свойство никогда не стоит после него. Отбрасываются явным списком
Events/Attributes/Commands/Parameters/CommandInterface и свойства, значение
которых — имя реквизита формы (ReportResult, DetailsData, VariantAppearance,
GroupList): реквизиты не заимствуются, ссылка повисла бы.
Заодно разобран конфаунд коммита 7abe26af, менявшего три вещи разом с одной
атрибуцией ошибки. По эталонам Конфигуратора («Добавить в расширение»):
- корневой CommandSet переносится дословно — вырезание было ошибкой;
- вложенный CommandSet выбрасывается ЦЕЛИКОМ, а не опустошается до пустого
контейнера, как делалось;
- RowPictureDataPath — путь к данным, а не индекс картинки: сохраняется при
заимствовании основного реквизита (Объект.Товары.РасхождениеЗаказ) и
вырезается без него (Список.DefaultPicture). Переехал в formBindingDataTags.
Спецификация форм утверждала, что свойства идут до CommandSet/AutoCommandBar —
именно это заблуждение и было закодировано. Заменено фактическим порядком
секций, выведенным из корпуса (0 нарушений на 30223 формах). Там же поправлены
значения перечислений AutoTime/UsePostingMode/ReportFormType, которых в корпусе
нет ни одного.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Фикстуры roundtrip-кейсов объявляли формат 2.17 (это 8.3.24) и при этом режим
совместимости Version8_3_27. Из-за этого verify-snapshots на автодетекте платформы
(8.3.24) не мог загрузить их вообще: «Для работы с конфигурацией необходима версия
платформы не меньше, чем 8.3.27». Падение выглядело регрессией навыка, а было
свойством фикстуры, и платформенная верификация девяти кейсов молча не работала.
Режим приведён к формату (Version8_3_24) во всех девяти фикстурах — они дублируются
по навыкам и обязаны оставаться копиями. Эталоны пересняты точечно: дифф —
ровно две строки на файл.
verify-snapshots без --v8path: role-compile 13/0, cf-edit 14/0, meta-edit 20/0,
meta-compile 79/0, subsystem-edit 9/0, xdto-compile 12/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прогон verify-snapshots вскрыл, что три новых кейса не доезжают до платформы:
роль ссылается на объекты, которых нет в фикстуре и которые не создаёт ни один
навык (веб-сервис, внешний источник данных, перерасчёт), а фикстуры валидатора —
это только каталог Roles/ без Configuration.xml. Объявлены skipPlatformVerify
с причиной, чтобы пропуск был видимым, а не молчаливым.
expect.filesAbsent внесён в таблицу ключей README: недокументированный ключ,
который понимает только один раннер, даёт тихую дыру.
Проверено: verify-snapshots на 8.3.27 — role-compile 13/0/1, role-validate 5/0/2;
число пропусков сверено с expected-skips на обеих ОС и обоих портах.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Неизвестный тип объекта обрабатывался веткой «warning» и всё равно попадал в
Rights.xml: и генератор, и валидатор рапортовали успех. Блок прав на тип, который
прав не имеет, платформа не отвергает — конфигурация с правами на перечисление не
загружается в информационную базу, конфигуратор зависает без сообщений.
Белый список — дерево редактора ролей: 27 типов (добавлен ExternalDataSource,
которого не было ни в одной таблице) плюс таблица видов вложенности с наборами прав
и привязкой вида к типу-родителю. Значения сняты с корпуса (acc/erp/ut/unf, ~2750
ролей) и с выгрузок роли со всеми проставленными правами — для внешнего источника
данных и перерасчёта регистра расчёта, которых в корпусе нет.
- role-compile: отказ ДО записи файлов, все причины разом, exit 1; ни файлов роли,
ни записи в Configuration.xml. Русские алиасы для типов без прав — ради внятного
отказа, а не ради генерации.
- role-validate: те же случаи — ошибка вместо предупреждения.
- Побочно снято 424 ложных предупреждения на корпусе: права Use у операций
веб-сервисов и методов HTTP-сервисов считались недопустимыми для вложенных объектов.
- Проверка имён прав и вложенных путей больше не пропускает мусор: неизвестное право
и путь вида Enum.Х.Attribute.Y раньше не проверялись вовсе.
- docs/1c-role-spec.md: ExternalDataSource ошибочно числился типом без прав.
- dsl-reference.md переписан под «что можно написать» (тип → права, виды
вложенности) вместо таблиц прощающего ввода.
Тесты: expect.filesAbsent в раннере; новые кейсы на отказ и на вложенные объекты.
Починены кейсы, которые ничего не проверяли: три задавали права одной строкой
"Read View" (навык писал в эталон несуществующее право), valid-role собирал роль
неизвестным ключом и валидировал пустую, bad-root проходил на «файл не найден».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа 8.3.23 доустановлена, замер тем же способом (пустая ИБ создаётся и выгружается
одной платформой) дал 2.16 — сообщение из issue #63 подтверждено собственным измерением,
пометка «по issue #63» из таблицы снята.
Незамеренными остались только 8.3.21/8.3.22, и пробел зажат с обеих сторон: 2.13 на 8.3.20
и 2.16 на 8.3.23 — ровно +3 версии на 3 релиза, то есть равномерный шаг подтверждается
арифметически.
Заодно расщепилась дельта пустой конфигурации ниже 2.17, которая раньше была снята одной
точкой: AllowedIncomingShareRequestTypes приезжает ровно в 2.17, DatabaseTablespacesUseMode
и DefaultReportAppearanceTemplate — где-то в 2.14–2.16. Гейт по версии ни одному из них не
нужен: это свойства корня конфигурации (реестр meta-validate — про объекты), а внутри
проверенного диапазона все три существуют всегда.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
«Не использовать» в Конфигураторе хранится как версия ТЕКУЩЕЙ платформы: свежая база
получает Version8_3_<своя> (замерено на 8.3.20/8.3.24/8.3.25/8.3.27/8.5.1), и ни одна из
пяти типовых в корпусе DontUse не содержит. Само значение легально — платформа принимает
его без ошибок, — но не выживает: на 8.3.25 и на 8.3.27 выгрузка возвращает Version8_3_8.
Контроль: тот же шаблон cf-init с явным Version8_3_25 роундтрипится без изменений.
Отсюда предупреждение, а не запрет: запрещать значение, которое платформа принимает, —
то же самое, от чего мы только что ушли в -FormatVersion. Канал тот же (stderr, exit 0),
что и у предупреждения о версии формата.
Абзац про DontUse убран из SKILL.md: инструкция читается всегда, а ловушка актуальна почти
никогда, и текст сам ЗНАКОМИЛ модель со значением, которое выбирать не следует.
Сравнение регистронезависимо явно в обоих портах: в PS -eq таков по умолчанию, в py — нет,
и молчаливое расхождение портов началось бы прямо здесь.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Счётчик был один: несобранное окружение давало «1 НАРУШЕНИЙ инварианта uuid» и
отправляло искать баг там, где его нет. Счётчики разведены, exit по-прежнему 1
в обоих случаях — непрогнанный порт это не «зелено».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Гард запускает навыки в обоих портах и зашивал `powershell.exe` и `python`. Вне Windows
он падал с «spawnSync powershell.exe ENOENT» и красил весь check-all, хотя PowerShell на
маке не исполняется в принципе — это природа платформы, а не пробел в покрытии.
PowerShell отсеивается по ОС с явной строкой о пропуске; интерпретатор python на *nix —
python3. Если запрошен только powershell и ОС не Windows, выходим с 1: молча зеленеть,
ничего не проверив, хуже, чем упасть.
Гард запускает НАВЫКИ, а им нужен интерпретатор с lxml — системный python3 на маке его не
имеет. На ошибке импорта печатается подсказка про PYTHON=<venv>, иначе окружение читается
как нарушение инварианта.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
«skipped» — норма, а не падение, но само число ни о чём не говорит, пока не с чем сверить,
а запоминать его нельзя: оно растёт с набором кейсов. Прежняя сверка жила в личной памятке
как grep по 'external:|runtimeOnly|osOnly' и уже сломалась — она считает скипом ЛЮБОЙ osOnly,
а posix-кейсы фейка платформы на маке как раз выполняются (grep давал 67 против 59 реальных).
Скрипт повторяет правила гейтинга раннера и живёт рядом с ним, поэтому расходиться им негде.
--list печатает пропуски поимённо с причиной. Сверено: win32/powershell 8, win32/python 11,
darwin/python 59 — совпало с прогонами.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Значение /Out несёт кавычки внутри токена — это соглашение 1С (run_v8 передаёт
File="путь" так же). В batch их снимает %~2, поэтому win32-фейк работал; в sh
их надо снять явно, иначе cp целится в имя файла с кавычками и лог не появляется.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кейсы детектора были написаны на batch-фейке и потому гейтились osOnly: win32. На маке
py-порт единственный, то есть ровно там, где проверять важнее всего, детектор оставался
без автоматического покрытия.
Добавлен sh-фейк и восемь зеркальных кейсов (osOnly: darwin/linux, runtimeOnly: python).
Для этого потребовались два расширения DSL:
- `writeFile.executable` — на *nix навык запускает платформу через exec, и файл без бита
исполнения не стартует вовсе; Node пишет файлы без +x. На Windows chmod — no-op.
Реализовано во всех трёх местах разбора шага (оба в runner.mjs и в verify-snapshots.mjs):
ключ, который понимает только один раннер, даёт тихую дыру.
- `osOnly` принимает массив, а не только строку. Иначе darwin и linux требовали бы двух
копий одного кейса.
Оба ключа описаны в tests/skills/README.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа рапортует об успехе (exit 0) и одновременно пишет в /Out-лог, что часть
метаданных отброшена. Детектор этого был только в db-load-xml, хотя db-load-git гоняет
тот же /LoadConfigFromFiles (и дописывает /UpdateDBCfg в тот же вызов), а db-update —
вторую половину той же цепочки. Оба лог печатали, но не разбирали.
Детектор извлечён в Find-SilentRejections / find_silent_rejections и внесён в реестр
семей check-inline-drift: инлайн-код гард сверять не умеет, а именно расхождение копий
и было бы главным риском такого дублирования.
Добавлен восьмой паттерн — «Для работы с конфигурацией необходима версия платформы не
меньше». Замерено при работе над issue #63: конфигурация с режимом совместимости выше
платформы грузится с кодом 0, db-update тоже отвечает 0, объекты в базу не попадают, а
отказ приходит только в рантайме. Строка обрезана до инвариантной части — конкретная
версия в сообщении меняется.
Текст предупреждения переписан. Убрана подсказка «pass -StrictLog to treat as error»:
ключ предназначен для регрессов (его передаёт verify-snapshots), а совет бессмысленный —
операция уже выполнена, и повторять её ради того же текста незачем. Формулировка больше
не утверждает «dropped properties/refs»: класс проблемы разный, а строки лога печатаются
следом и говорят за себя.
Две правки по дороге:
- `return ,$found` в паре с `@()` у вызывающего давал массив из одного пустого массива,
то есть предупреждение «1 problem(s)» на чистом логе. Возврат без запятой-обёртки.
- py-порт писал предупреждение в stderr, PS1 — в stdout. В самом py-порте stderr занят
исключительно фатальными «Error:» перед exit 1, так что не-фатальное предупреждение там
было единственным исключением. Приведено к stdout — и к соглашению своего же файла, и к
поведению PS1; verify-snapshots на падении читает `stderr || stdout`, поэтому диагностика
не теряется.
Тесты: фейковая платформа .cmd, которая вычитывает путь из /Out и кладёт туда готовый лог,
— по четыре кейса на db-load-xml и db-update (отбраковка, она же под -StrictLog, новый
паттерн, чистый лог без предупреждения). Раньше детектор не был покрыт вообще.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Допустимый список версий был независимым литералом в десяти файлах, и сверять его
было не с чем. Именно так волна 2.21 прошла по четырём валидаторам и молча обошла
пятый — form-validate остался на 2.17–2.20 (issue #63).
check-format-versions.mjs держит три инварианта: границы диапазона одинаковы во всех
навыках и на обоих портах; дефолт -FormatVersion у *-init лежит внутри диапазона;
верхняя граница совпадает с последней ЗАМЕРЕННОЙ ступенью таблицы §7.1 из
1c-configuration-spec.md — так расхождение спеки и кода падает здесь, а не на чужой
выгрузке. Отдельно ловится возврат ValidateSet/choices в *-init.
Get-FormatRank разъехался бы по восьми новым копиям — они внесены в реестр семьи
format_rank в check-inline-drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Заглушечная конфигурация зашивала version="2.17" и CompatibilityMode=Version8_3_24
независимо от собираемых исходников. Ограничение платформы одностороннее — она читает
формат не новее себя, — поэтому на 8.3.20 такая заглушка не грузилась вовсе:
«Неизвестная версия формата 2.17 загружаемого файла».
Режим совместимости бил тише и потому опаснее: платформа рапортовала успешную
загрузку с кодом 0, писала в лог «Для работы с конфигурацией необходима версия
платформы не меньше, чем 8.3.24» и не создавала объекты — сборка падала уже на
«Неизвестное имя типа», уводя диагностику в сторону.
Оба значения выводятся из версии исходников. Для версии взят min(исходники, 2.17):
заглушке нужна самая низкая работающая версия, а не версия исходников, поэтому на
2.17+ поведение остаётся прежним и опускается только под 2.13–2.16. Режим — по той же
лестнице.
Проверено на живой платформе: обработка 2.13 со ссылочным реквизитом собирается на
8.3.20 обоими портами; для исходников 2.17 заглушка по-прежнему 2.17/Version8_3_24.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Валидаторы объявляли «ожидаемым» литеральный список версий и ругались на всё
остальное как на подозрительное. Но 2.13–2.16 не подозрительны — они просто не
проверялись, а form-validate вдобавок отстал на 2.21 и предупреждал о форме, которую
сам же создаёт цепочкой epf-init 2.21 → form-add (issue #63).
Теперь сравнение числовое, через общий Get-FormatRank, и исходов три: ниже 2.17 и
выше 2.21 — предупреждение «not verified on it», нечисловое значение — ошибка. Раньше
мусор вида «abc» и реальная версия 2.16 давали одно и то же предупреждение.
В *-init ValidateSet/choices сняты: версия вне диапазона больше не запрет, а
предупреждение в stderr — скаффолд выпускается. Опечатка (2,17) остаётся ошибкой.
Предупреждение печатается напрямую в stderr и после настройки кодировки консоли:
Write-Warning в PS 5.1 уходит в stdout, получает локализованный префикс и перенос по
80 символов, а до [Console]::OutputEncoding em-dash уезжал в вопросы — оба расхождения
ломали паритет с py-портом.
Отдельно в cf-init исправлен гейт TextToSpeech: свойство приезжает форматом 2.18
(8.3.25), а не 2.21. Замер: выгрузки пустой ИБ шести платформ — 37 записей
UsedMobileApplicationFunctionalities на 2.13/2.17 и 38 на 2.18–2.21. Ролики загрузки
показали, что это настоящее новое свойство, а не смена дефолта эмиссии: на 2.17 тег
роняет загрузку XDTO-ошибкой (8.3.24), значение значимо (true переживает роундтрип),
а пропуск на 2.18+ разводит роундтрип — платформа допишет тег сама. Снапшоты трёх
кейсов meta-compile обновлены: их фикстуры строятся cf-init, дельта — только вставка
тега.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
execSkillAsync отдавал только stdout, а stderr сохранялся исключительно в ветке
ошибки. Поэтому предупреждение навыка, который отработал успешно (exit 0), проверить
было нечем: кейс мог убедиться лишь в том, что навык не упал, — то есть в молчании
вместо текста.
Резолв теперь отдаёт оба потока, добавлен ключ expect.stderrContains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Утверждение «2.17 = платформы 8.3.20–8.3.24» повторялось в 7 местах и было неверным:
точка «8.3.20» получена не замером, а из имени каталога cfsrc/acc_8.3.20, который
побайтово совпадает с acc_8.3.24 (91807/91807 файлов; обе — БП 3.0.181.31 с режимом
Version8_3_24, который 8.3.20 открыть не может). Интерполяция между двумя одинаковыми
точками и дала мнимый интервал.
Замер пустыми ИБ на шести установленных платформах (создать базу платформой X и
выгрузить ею же): 8.3.20 → 2.13, 8.3.24 → 2.17, 8.3.25 → 2.18, 8.3.26 → 2.19,
8.3.27 → 2.20, 8.5.1 → 2.21. Версия меняется каждым релизом.
Полная лестница теперь в одном месте — §7.1 1c-configuration-spec.md, с колонкой
«замерено»: 8.3.21/8.3.22 не проверялись, 2.16 подтверждена только сторонним
сообщением. Остальные спеки на неё ссылаются.
Заодно сняты два вывода, выведенных из каталога-дубля: «содержимое Form.xml идентично
между 8.3.20 и 8.3.24» и строка «8.3.20 | 2.17 | Базовая» в спецификации ролей.
Дописан 2.21 в таблицы, которые о нём не знали, и уточнён TextToSpeech: он приезжает
в 2.18, а на 2.17 роняет загрузку XDTO-ошибкой, а не отбраковывается молча.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сквозной прогон (собрали расширение навыками → загрузили → обновили БД →
выгрузили обратно) показал: записанный нами <FoldersOnTop> из выгрузки
пропадает. Платформа его у заимствованной оболочки не хранит — принимает
молча и выбрасывает. Соседние свойства того же списка (Hierarchical,
CodeLength, DescriptionLength, CodeType, CodeAllowedLength) сохраняются,
так что дело именно в этом свойстве. Эталон Конфигуратора его тоже не
переносит.
Список propsToExtract собирался на глаз при появлении -BorrowMainAttribute;
это второе свойство из него, которое платформа не принимает (первым был
NumberPeriodicity, ронявший UpdateDBCfg).
Одноразовый прогонщик сценариев — debug/roundtrip/ (в .gitignore).
Регресс cfe-borrow 12/12 на обоих рантаймах, гарды зелёные.
Корпусный прогон (УТ/БП/ERP, 21 097 форм) после понижения Command/Action
оставлял 8 форм с ошибками. Разобраны все, дефектов оказалось два.
1. Вложенная таблица. Путь Items.<Таблица>.CurrentData.<Поле> разрешался
ОДНИМ шагом: если таблица сама привязана через Items.*, корнем
оставался литерал «Items», и типовая форма объявлялась битой
(НастройкаПравилОбработкиЗаявокСотрудников в БП и ERP). Теперь
разрешение идёт цепочкой, со страховкой от кольца ссылок.
2. AutoCommandBar с обычным id вместо -1. Это соглашение, а не требование:
21 094 формы из 21 097 используют -1, но три платформа выгружает с
обычным id и грузит их без нареканий. Понижено до предупреждения;
ошибка осталась на случай, когда id вообще не число.
Оставшиеся три формы — дубли id элементов и команд. Это НЕ ложные
срабатывания: измерение по корпусу показало ровно по одному случаю на
21 097 форм, то есть опечатки вендора, а не структурное правило (были бы
пулы id раздельными, пересечений были бы тысячи). Плюс кейс duplicate-id
прямо требует считать дубль ошибкой.
Итого по корпусу: 283 формы с ошибками → 3.
Заодно дооформлена фикстура cfe-borrow/container-types (пространство имён
веб-сервису, документ журналу и последовательности): verify-snapshots по
cfe-borrow теперь 12/12.
Проверка версии читала Configuration.xml собственной регуляркой. Отсюда два
пробела: форма автономной внешней обработки не проверялась вовсе (своего
Configuration.xml у неё нет, версию несёт корень обработки), а разбор
дублировал уже существующий эталон.
Теперь используются копии общих эталонов — detect_format_version (авторитет
form-compile) и support-guard: is_external_root (авторитет cf-edit), оба
навыка добавлены в реестр check-inline-drift.
Попутно вскрылось следствие для соседней проверки: обработка, чьи исходники
лежат внутри дерева с Configuration.xml (обычная раскладка src/cf рядом с
src/epf), считалась «конфигурационным контекстом», и Check 12 ругался на её
собственные External*-типы. Теперь климб останавливается на ближайшем
якоре — том же правиле границы автономного объекта, что в support-guard.
Плюс паритет: PS отчитывался «Data bindings: none», PY эту ветку не имел.
Кейс на EPF-контекст добавлен. Регресс 16/16 на обоих рантаймах, корпус
21 097 форм без новых срабатываний, гарды зелёные.
Обе проверки — про ошибки, которые делает не платформа, а тот, кто пишет
XML руками. Обе вскрыты на наших же фикстурах, обе платформенно-фатальны:
файл не читается вовсе, а прежний валидатор говорил OK.
Check 13 — префикс в значении типа обязан резолвиться. `cfg:CatalogRef.X`
в <v8:Type> при незадекларированном xmlns:cfg даёт «Исключение XDTO при
чтении файла». Область видимости считается по узлу, а не по корню:
локальная xmlns на элементе законна и в типовых встречается (d4p1, mxl).
Check 14 — версия формата формы против версии конфигурации. В пределах
одной выгрузки версия едина; форма из более новой выгрузки даёт
«Неизвестная версия формата N загружаемого файла».
Ложных срабатываний нет: корпус УТ/БП/ERP, 21 097 форм — те же 8 форм с
ошибками, что и до правки. Регресс 15/15 на обоих рантаймах, паритет
портов сверен построчно.
verify-snapshots считает фикстуру готовой конфигурацией и грузит её в базу,
а фикстуры form-validate состояли из одного Form.xml — поэтому по этому
навыку верификация падала целиком и по факту не выполнялась никогда.
Что вскрылось при доведении, по нарастающей:
1. Нет Configuration.xml и объекта — «Файл объекта не существует».
Дописаны cf-init + meta-compile + form-add, рукописный Form.xml сохранён.
2. Префикс `cfg:` в значении <v8:Type> при НЕобъявленном xmlns:cfg —
«Исключение XDTO произошло при чтении файла». Тот же класс, что ишью #38,
только в наших собственных тестовых данных: четыре фикстуры платформа не
читала вовсе, а кейсы на них считались зелёными.
3. Привязка Объект.Наименование у обработки — у неё нет такого стандартного
реквизита. Добавлен обычный реквизит.
4. Динамический список без источника — «Неверный путь к данным: Список.Ссылка».
Добавлен справочник-источник и MainTable.
Кейс с формой расширения помечен skipPlatformVerify: верификатор грузит
каталог кейса как конфигурацию, фрагмент расширения так не проверить. Ключ
существовал в verify-snapshots, но не был описан — добавлен в README.
form-validate: 14/14 на обоих рантаймах, платформенная верификация 13/14
(один пропуск с причиной, было 10/14 с четырьмя падениями).
Проверка объявляла ошибкой любую команду формы без <Action>. Корпусный
прогон (УТ 8.3.27, БП 8.3.27, ERP 8.3.24 — 21 097 форм) показал 406 таких
команд на 275 формах, и все они произведены самой платформой: формат это
допускает, конфигурации грузятся.
Приём типовых: действие назначается в рантайме, в ПриСозданииНаСервере —
`Команда.Действие = "Подключаемый_" + Имя + "Локализация"`. Назначать может
и чужой модуль (переопределяемый слой, подключаемые команды), поэтому по
одному Form.xml вердикт не вынести — отсюда предупреждение, а не ошибка, и
никакого подглядывания в соседний модуль.
Корпус после правки: форм с ошибками 283 → 8, новых срабатываний нет.
Оставшиеся 8 — четыре других класса, разбираются отдельно.
Кейс с фикстурой: команда без Action на кнопке → предупреждение, exit 0.
PS-порт падал с «Не удается индексировать в массив NULL»: @($null).Count в
PowerShell равен единице, поэтому ветка самозакрывающегося тега была
недостижима, и код шёл эмитить несуществующую колонку. PY на том же входе
молча писал пустую группу — портируемого поведения не было вовсе.
Разведены два случая, которые до сих пор путались:
- `"columns": []` — явно пустая группа, законная форма (платформа так
пишет таблицу без доп. колонок). Работает как работала, self-closing;
- ключа `columns` нет вовсе — недосказанность автора: «доп. колонки есть»,
а какие, не сказано. Теперь отказ с указанием, что делать.
Валидатор такое по-прежнему пропускает: в выгрузке платформы пустая группа
встречается, запрет на авторинг не равен запрету на существование.
Заодно кейс cfe-borrow/form-main-attr-columns вернулся к форме эталона
Конфигуратора — таблица без колонок описана явно пустым списком, как в
выгрузке, а не обходным манёвром вокруг падения.
На одной и той же форме PS сообщал 12 проверок, PY — 9. Расходился не
вердикт, а учёт: три проверки (ссылки команд, обработчики событий,
действия команд) в PS отчитываются строкой «none», когда проверять нечего,
а в PY эта ветка отсутствовала — и проверка не попадала в счётчик.
Добавлены недостающие ветки. Выборка из 40 форм корпуса (УТ, БП, ERP):
число строк отчёта совпадает у портов на всех сорока.
Платформа считает это свойство модификацией настроек нумерации и тогда
требует объявить ещё и <Numerator/>: /UpdateDBCfg падает с «Для
заимствованного документа, настройки нумерации которого модифицированы,
отключать контролируемость свойства "Нумератор" недопустимо». Загрузка при
этом проходит — ошибка вылезает только на обновлении конфигурации БД,
поэтому ручной E2E её не видел, а verify-snapshots поймал.
Эталон Конфигуратора переносит NumberType/NumberLength/NumberAllowedLength
и не переносит NumberPeriodicity — приводим к тому же. Свойство попало в
список ещё при появлении -BorrowMainAttribute, набор тогда был угадан, а
не выверен по эталону.
Заодно две мои фикстуры доведены до платформенной валидности:
form-choice-param-links задавала в ИСХОДНОЙ форме ссылку на несуществующий
реквизит — такая конфигурация не грузится сама, проверять на ней вырезание
висячей связи нельзя; gentypes-full не имела регистратора для регистра
бухгалтерии и задачи для бизнес-процесса.
verify-snapshots по cfe-borrow: 11 из 12 (container-types падал и раньше,
дефект его фикстуры). Регресс 12/12 на обоих рантаймах, все гарды зелёные.
В примерах имя расширения задано явно (-Name Расш1), а рядом стоял
плейсхолдер -OutputDir src\cfe\extname — связь между именем и каталогом
из такого примера не читается. Теперь каталог называется по имени, и
конвенция «подкаталог = имя расширения» видна из самих примеров.
Базовая команда дополнена -OutputDir и -ConfigPath: без них расширение
уезжает в src (тот самый неоднозначный каталог), а совместимость и UUID
языка берутся по умолчанию вместо базовой конфигурации.
В группе cfe-* примеры расходились: cfe-patch-method уже использовал
src\cfe\<расширение> и src\cf, остальные три навыка — голый src и
абсолютный C:\cfsrc\erp. Голый src особенно вреден: в репозитории с
конфигурацией и расширением он неоднозначен, и модель подставляет в
-ExtensionPath конфигурацию.
Везде одна пара: src\cfe\extname и src\cf.
Заодно по итогам появления -ConfigPath у cfe-validate: блоки «Верификация»
теперь показывают его передачу, а описание параметра в самом валидаторе
переписано с устройства проверки на повод её включить — с последствием
(расширение пройдёт валидацию и будет отвергнуто платформой) и с
алгоритмом поиска пути прямо на месте, без отсылки к соседнему навыку.
У cfe-init отмечено, что CompatibilityMode не нужен при заданном
ConfigPath, и что расширение стоит класть в отдельный подкаталог.
Три вещи, на которых платформа отвергала расширение, а валидатор молчал.
Check 9 — полнота набора GeneratedType у заимствованной оболочки: неполный
набор платформа не читает («отсутствует один или более типов объекта»).
Карта категорий взята из той же таблицы спецификации (§2.5) и заведена в
реестр check-type-maps.mjs, чтобы копия не разошлась с остальными.
Check 12 — <AdditionalColumns table="Объект.X"> при незаимствованной
табличной части. В отличие от соседних проверок блока, здесь сигнал точный
(имя из атрибута), а последствие жёсткое, поэтому ошибка, а не warning.
Check 14 — пути Объект.* заимствованных форм против конфигурации-источника,
по новому опциональному -ConfigPath. Без него проверка пропускается с явной
строкой в отчёте. Отличить живой путь от висячего иначе нельзя: Объект.Партнер
валиден и без заимствования (наследуется от базы), а Объект.Товары.Артикул не
разрешится нигде. Итоги колонок (Total<Колонка>) и стандартные реквизиты
пропускаются — иначе ложные срабатывания на типовых формах.
Проверено на пяти расширениях: наши (оба режима) и оба эталона Конфигуратора
проходят чисто, расширение с дефектом ловится. Регресс cfe-* 51/51 на обоих
рантаймах, гарды дрейфа зелёные.
Форма, которую платформа отвергала с «Неверный путь к полю - Объект.Партнер»,
проходила валидацию с вердиктом OK. Check 5 такое не видит по двум причинам:
у формы с BaseForm он пропускает базовые элементы (id < 1000000), а привязка
внутри <ChoiceParameterLinks> лежит в <xr:DataPath> и в его список тегов не
входит вовсе.
Проверка 11d: если форма не объявляет основной реквизит, любой путь с корнем
«Объект» не разрешится — ошибка. Непрозрачные формы пути (1/0:uuid), которыми
как раз и заменяет такие ссылки Конфигуратор, ошибкой не считаются.
Две фикстуры: висячая ссылка ловится, та же форма в uuid-форме проходит.
Регресс 13/13 на обоих рантаймах.
Заимствование формы без основного реквизита копировало
<ChoiceParameterLinks>/<xr:Link> как есть, с текстовым путём
«Объект.Партнер». В расширении такой путь не разрешается — платформа
отвергала загрузку: «Неверный путь к полю - Объект.Партнер». Привязка
лежит в <xr:DataPath> внутри xr:Link, и общий стриппинг её не видел.
Конфигуратор ссылку не выбрасывает, а переводит путь в непрозрачную форму
«1/0:<uuid реквизита объекта>» — связь остаётся рабочей. Делаем так же;
реквизит, которого в источнике нет, недоступен и по uuid — такую связь
вырезаем вместе с опустевшим контейнером. Путь односегментный во всём
корпусе УТ (285 из 285), глубже не бывает.
Результат совпал с эталоном Конфигуратора вплоть до uuid, E2E на UT_DEMO:
«Load completed successfully». Регресс 12/12 на обоих рантаймах.
Заимствование формы с -BorrowMainAttribute синтезировало <Attribute
name="Объект"> с нуля — Type/MainAttribute/SavedData и всё. Секции
<UseAlways> и <Columns> исходной формы терялись, а вместе с ними и
дополнительные колонки табличных частей, объявленные прямо в форме:
платформа отвергала загрузку («Неверный путь к данным: Объект.Товары.Артикул»).
Три связанных следствия:
- секции переносятся из исходной формы (helper Get-MainAttributeExtraXml
на оба порта, отступ в BaseForm — тем же приёмом, что у ChildItems);
- табличная часть, упомянутая только в <AdditionalColumns table="…">,
теперь попадает в сбор путей и заимствуется — иначе «Колонки не могут
быть добавлены к реквизиту»;
- типы колонок дозаимствуются через collect_reference_types, как это
делает Конфигуратор (DefinedTypes/Артикул в эталоне).
Попутно снят PS↔PY-дрейф: lxml включал хвостовой пробельный узел в
tostring, из-за чего .py вставлял пустые строки, которых нет у .ps1
(with_tail=False в четырёх местах).
E2E на UT_DEMO (8.3.27, формат 2.20, форма заказа поставщику): режим с
основным реквизитом грузится «Load completed successfully» без ручных
правок. Регресс 11/11 на обоих рантаймах.
Платформа отвергала заимствованный план видов характеристик: «отсутствует
один или более типов объекта ChartOfCharacteristicTypes». В карте
$script:generatedTypes не хватало категории Characteristic — а вместе с ней
ещё пяти категорий у пяти типов (планы счетов и видов расчёта, регистры
бухгалтерии и расчёта, бизнес-процессы).
Дефект был чистым дрейфом копий: карта живёт в трёх навыках, и в
meta-compile с meta-validate она верна. Чтобы расхождение больше не
копилось молча, каноническая таблица наборов GeneratedType заведена в
спецификации (§2.5), а check-type-maps.mjs получил виды gentypes/gencats и
сверяет по ней все три карты.
Регресс cfe-* 47/47 на обоих рантаймах, оба гарда дрейфа зелёные.
Верификатор читает тот же DSL, что и функциональный раннер, но своей
реализацией, и отставал на пять ключей: preRun[].cwd, inputFrom, cwd на уровне
кейса, раскрытие {workDir} в args_extra и маппинг from: outputPath. Кейсы,
опирающиеся на них, до платформы не доезжали вовсе — падали на подготовке
фикстуры, причём шаг preRun с относительным путём писал её в корень репозитория.
Незнакомый from теперь роняет кейс с внятным сообщением: молчаливый default
маскировал расхождение под дефект навыка (флаг уходил без значения).
Платформенная верификация: mxl-compile 28/36 -> 36/36, mxl-decompile 0/7 -> 7/7,
mxl-info 2/7 -> 7/7, mxl-validate 0/4 -> 4/4, skd-decompile 0/17 -> 17/17.
Оба ключа, которых не было в документации DSL, дописаны в README.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Спецификация XML — дописано то, что вскрыли контролируемые макеты и замеры
по корпусу:
- шрифт-ссылка на СИСТЕМНЫЙ шрифт: префикс sys в корне не объявлен, поэтому
объявление xmlns дописывается прямо на узел. Плюс правило вывода kind
из префикса и то, что неиспользуемый шрифт в палитру не попадает;
- новый раздел «Устройство палитр»: порядок документный и НЕ зависит от
последовательности действий автора (проверено опытом с оформлением снизу
вверх), формат по умолчанию последний, палитра дедуплицирована по содержимому;
- у текста ячейки ТРИ состояния: тега нет, тег с элементами, пустой <tl/>.
Третье — 57% макетов корпуса;
- языковые настройки: набор языков не выводится из языков текста, description
бывает самозакрывающимся, currentLanguage бывает отсутствующим и бывает
указывающим на необъявленный язык.
Спецификация DSL — из ограничений убрано объявление языков макета: оно больше
не теряется. Добавлено пояснение, почему побайтовое совпадение достижимо не на
любом макете: в долго правленных макетах остаются следы прежних состояний,
которые из итогового документа не выводятся.
В инструкции навыка отражена только форма шрифта-ссылки — остальное из этой
серии либо уже там, либо для авторинга не нужно.
Примеры из справочника скомпилированы и проверены валидатором.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Шрифт бывает не собственным описанием, а ссылкой: на элемент стиля конфигурации
(style:) или на системный шрифт (sys:). Своих атрибутов у такого шрифта нет, и мы
превращали его в пустую запись — faceName="" height="0". ps1 при сборке подставлял
туда Arial 10, то есть подменял данные молча.
В корпусе таких шрифтов 272 в 213 макетах из 10 924: StyleItem 209, WindowsFont 63.
Запись — та же, что у шрифта в описании формы: { "ref": "style:TextFont" }.
kind выводится из префикса, ключом быть не обязан.
Синтетический стенд показал деталь, которую по корпусу было не разглядеть: префикс
sys в корне документа не объявлен, поэтому платформа дописывает объявление xmlns
прямо на узел шрифта — тот же приём, что с цветами из web-палитры.
Порты после этого сошлись ПОЛНОСТЬЮ: на пилоте из 40 макетов совпадают и JSON
декомпиляторов, и собранный XML. Макет со шрифтами добавлен в побайтовую
регрессию, стенд 8 из 13.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
У текста ячейки три состояния, а выражались два: тега нет вовсе, тег с пустым
текстом на каждый язык — и третье, <tl/>, которое мы теряли. Это не редкость:
38 075 ячеек на 1200 макетов, встречается в 57% макетов корпуса.
Выражается пустым объектом: "text": {}. Не новый ключ и не новое понятие, а
вырожденный случай уже принятой записи «язык → текст» — языков нет вовсе.
В позиционной строке двусмысленности не создаёт: {} не несёт ключей ячейки,
значит по общему правилу читается как текст, а null там по-прежнему «пропустить
колонку».
В описании DSL записи нет намеренно. Для авторинга она бесполезна — визуально
это тот же пустой текст, что и "", — а документировать две пустые формы рядом
значило бы завести развилку, у которой нет правильного ответа.
Платформа пишет такой тег самозакрывающимся, поэтому эмиссия отдельной веткой.
На пилоте потери в категории row[].cell[].tl упали с 12 488 до 2, заодно
row[].cell[].fmt с 41 645 до 30 088; совпадение строк документа 17.6% → 18.5%.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Два дефекта, найденных при сведении портов.
Стиль, на который ссылалась только дополнительная колоночная раскладка,
отсекался как неиспользуемый: проверка смотрела в result, а columnSets
попадает туда ПОЗЖЕ неё. Ссылка оставалась висячей — 5 макетов пилота
из 40 указывали на стиль, которого в styles нет. Берём стили из самих
раскладок.
Именованные элементы платформа хранит отсортированными по имени ординально.
ps1 сортировал их Sort-Object -CaseSensitive, а он всё равно сравнивает по
текущей культуре — комментарий рядом сам об этом предупреждал. На кириллице
порядок расходился с py-портом (4800 строк разницы на одном макете).
Сортируем по ключу из кодов символов: в нём только 0-9A-F, культура его
переупорядочить не может.
Порты сошлись: JSON декомпиляторов совпадает на всех 40 макетах пилота
(было 26 расхождений), собранный XML — на 39 из 40 (было 20 расхождений).
Остаток — один макет, где расходятся компиляторы: ps1 подставляет
faceName="Arial" height="10" там, где py пишет пустой шрифт.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Расхождение JSON между портами было 26 макетов из 40, стало 5. Расхождение
собранного XML — 20 из 40, стало 7. Причин оказалось четыре, и три из них
дефекты, а не просто разнобой.
1. ps1 грузил XML с PreserveWhitespace = $false, и текст ячейки из одних
пробелов схлопывался в пустой. Молчаливая потеря данных в каноничном
порте, 25 макетов пилота.
2. Стили обнаруживались обходом хэш-таблицы строк, а её порядок в PowerShell
НЕ определён. Платформа же кладёт записи палитры в порядке документа, так
что порядок обхода — часть верности вывода, а не деталь. Обход теперь по
возрастанию номера строки. ([ordered] тут не годится: с целочисленными
ключами он индексируется по позиции, а не по ключу.)
3. Именованные области сортировались нестабильным Sort-Object (-Stable
появился только в PowerShell 6.2), поэтому области с одинаковыми границами
получали произвольный порядок. Добавлен явный ключ исходного порядка —
в оба порта, чтобы совпадение было по построению, а не по совпадению.
4. containsValue / valueType / controlType протекали в styles сквозным
пробросом неизвестных тегов. Это свойства ЗНАЧЕНИЯ ячейки, а не оформления,
и компилятор таких ключей не знает — в DSL они были чистым шумом. Заодно
вложенный элемент больше не читается как скаляр: ps1 брал InnerText и
получал склейку поддерева, py брал .text и получал пустоту.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прежняя формулировка противопоставляла «ячейку» и «многоязычный текст», хотя
текст тоже даёт ячейку. Разница не в этом: объект либо описывает СВОЙСТВА
ячейки (среди ключей есть ключ её схемы), либо является её ЗНАЧЕНИЕМ, и тогда
ключи — идентификаторы языков.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В позиционной записи строки элемент — это значение содержимого ячейки, а значение
текста по общей конвенции бывает строкой либо объектом «язык → текст». Значит
объект {ru, en} там законен так же, как строка, и новой формы это не заводит.
Раньше ячейка с многоязычным текстом не считалась «простой», поэтому строка
двуязычного макета оставалась объектной:
{ "cells": [{ "text": { "ru": "Пусто", "en": "Empty" } }, { … }] }
[{ "ru": "Пусто", "en": "Empty" }, { … }]
Объект-элемент читается как ячейка, если несёт хоть один её ключ, и как текст
в противном случае: идентификаторы языков с ключами ячейки не пересекаются
(в корпусе это ru, en, ru1, Русский).
Попутно исправлен признак «список позиционный»: он искал элемент-строку, поэтому
строка из одних многоязычных текстов позиционной не признавалась и ключ cells
у неё оставался.
Правка не должна менять скомпилированный XML — и не меняет: пилот из 40 макетов
собрался побайтово так же, JSON при этом изменился у 13. Стенд 7 из 10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Две правки читаемости DSL, обе замечены на живом выводе.
Ячейка получала "style": "default" — ссылку на стиль, которого в styles нет
вовсе. Условие сверялось с обнаруженным стилем пустых колонок, а он бывает
равен "default": строка такой стиль не пишет, а ячейка получала ключ на
пустое место. Теперь сверяемся со стилем, который РЕАЛЬНО раздаётся ячейкам.
Позиционная запись строки отбрасывалась, как только первая ячейка стояла не
в первой колонке. Но один-два null впереди обычно короче объектной записи
с col: строка вида { "cells": [{ "col": 2, "text": "…" }] } сворачивается
в [null, "…"]. Выбираем ту форму, которая короче.
Правка не должна менять скомпилированный XML — и не меняет: пилот из 40
макетов собрался побайтово так же, как до неё, при том что JSON изменился
у 31 макета. Стенд по-прежнему 7 из 10 байт в байт.
Попутно блок JSON-сериализатора в ps1 перенесён выше первого использования:
в PowerShell функция должна быть объявлена до вызова.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
До сих пор строгая дорожка кампании жила только в отладочных прогонах, и её
некому было защищать: снэпшот сравнивает наш вывод с нашим же прежним выводом,
поэтому дрейф от платформы он не ловит.
Семь макетов, собранных вручную в Конфигураторе, положены в фикстуры. Кейс
декомпилирует такой макет и компилирует обратно, а результат сверяется с
исходником побайтово. Проверено, что сеть кусается: подмена одного тега в
фикстуре роняет кейс.
Раннеру добавлены две ручки:
- expect.filesEqual — сравнение двух файлов побайтово. Снэпшот его не заменяет:
--update-snapshots молча принял бы расхождение с платформой;
- inputFrom — брать вход из файла в рабочем каталоге, а не из case.input.
Нужно, когда вход производит preRun: case.input пишется ПОСЛЕ preRun и затёр
бы его.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Контролируемый стенд (upload/epf/МакетТабличныйДокумент) вскрыл четыре
системных расхождения подряд. После правок 7 макетов стенда из 10 проходят
раундтрип БАЙТ В БАЙТ обоими портами — до этого ни один.
1. Объявление языков макета жёстко писалось как «только русский». Это не то же
самое, что textLanguages: почти во всех макетах ERP объявлен один ru, а текст
лежит и под ru, и под en, поэтому выводить одно из другого нельзя. Заведены
недокументированные ключи languages / currentLanguage / defaultLanguage —
автору они не нужны, нужны раундтрипу. defaultLanguage несём, а не выводим:
«всегда ru» — наблюдение на четырёх русских типовых, а не правило формата.
2. <font> писался в каждом формате. Платформа пишет его только когда шрифт
задан: треть форматов корпуса (23 003 из 69 581) обходится без него.
3. Декомпилятор считал шрифт с индексом 0 «обычным» и не писал его в стиль.
Нулевой шрифт вовсе не обязан быть обычным — в макетах стенда он курсивный
и жирный, и начертание терялось.
4. Формат, несущий ТОЛЬКО шрифт, выглядел пустым: у ячейки он получал
зарезервированное имя default и не записывался, у строки терялся целиком.
Остаток на стенде — три макета, и все три про одно: ссылка ячейки на запись
ширины, оставшуюся от прежнего состояния документа. Это не воспроизводится
принципиально, разобрано в docs/1c-spreadsheet-spec.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Мы всегда заводили шрифт Arial 10 по умолчанию, даже когда его никто не
использует. Платформа так не делает: у макета без оформления элемента <font>
нет вовсе. Неиспользуемые шрифты теперь отбрасываются, ссылки перенумеровываются
(индексы шрифтов позиционные).
Проверено на контролируемом стенде: после правки простейший макет расходится
с платформенным ровно одной строкой — объявлением языков в шапке.
Кейс font-fractional-size дополнен: стиль, задающий ТОЛЬКО шрифт по умолчанию,
равнозначен отсутствию оформления (ячейка получает <f>0</f>), поэтому шрифт
остаётся неиспользованным. Чтобы кейс продолжал проверять целый размер, стилю
добавлено второе свойство.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Мы регистрировали формат по умолчанию первым, из-за чего вся палитра шла
со сдвигом относительно платформенной. На корпусе он последний в 8285 макетах
из 10 863, первым — в 25.
Подтверждено контролируемым стендом: в макетах, где ширины заданы на уровне
документа, палитра идёт «ширины колонок в порядке колонок, умолчание последним».
Порядок палитры не виден в семантическом диффе (правило palette-index разрешает
ссылки и тем самым его прячет), поэтому мерялся отдельно: на пилоте совпавших
с оригиналом записей с начала палитры стало 467 из 2274 против 23, палитр,
совпавших целиком, — 4 против 2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
За кампанию XML-уровень изучен заметно глубже, чем был описан. Внесено то,
что проверено на корпусе ERP (10 924 макета) и на контролируемом стенде:
- <i> платформа пишет только при разрыве последовательности;
- <indexTo> схлопывает только ПУСТЫЕ строки — прежняя формулировка «строки
с одинаковым содержимым» неверна: одинаковых непустых схлопнутых нет ни одной
при 98 153 несхлопнутых;
- <f>0</f> — у ячейки формата нет вовсе, это не индекс записи;
- канонический порядок тегов внутри <format> (height раньше width);
- единица ширины — 1/8 символа;
- свёртка четырёх одинаковых сторон рамки в <border>;
- цвет — значение с префиксом пространства имён, web/win объявляются прямо
на узле; в Form.xml те же цвета выглядят как web:/win:;
- формат строки несёт не только высоту, а формат колонки не только ширину;
оформление строки материализуется и в ячейки, кроме hidden;
- columnsItem с formatIndex 0 и с индексом за пределами size;
- полный список стилей линии вместо Solid/None.
Отдельно описана ловушка: width в формате ЯЧЕЙКИ — устаревшая ссылка, она не
описывает итоговое состояние документа и воспроизведению не подлежит.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
У строки есть собственный формат: платформа хранит в нём скрытие (17 423 вхождения
в корпусе), шрифт (9 216), фон, выравнивания, защиту. Мы писали туда только высоту,
всё остальное теряли.
Разбор на контролируемом стенде показал, что это два разных случая, а не один.
Оформление, применённое к строке целиком, платформа пишет И строке, И каждой
ячейке (backColor: 13 899 ячеек повторяют против 96). Скрытие — только строке
(24 026 против 62 856). Поэтому одного правила «rowStyle красит ячейки» мало.
Теперь rowStyle — стиль строки: по умолчанию ложится и на строку, и на ячейки,
как это делает платформа. Объектная форма { style, apply } задаёт исключения:
"row" — только строке, "cells" — только ячейкам. Модификатор нужен раундтрипу,
в описании DSL его нет.
Скрытие и высота — собственные свойства строки, ключами рядом: у ячейки таких
свойств не бывает, и в её оформление они не попадают.
На пилоте категория row[].formatIndex упала с 1964 до 201 потерянного факта.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ячейка без собственного оформления ссылалась на формат по умолчанию. Платформа
в этом случае пишет <f>0</f>, где ноль — не индекс записи, а «формата нет»:
на корпусе так у 170 710 ячеек против 50 635, ссылающихся на умолчание, и
<f>0</f> встречается в 71% макетов.
Подтверждено контролируемым стендом: в макете, где колонкам заданы ширины, а
ячейки ничем не оформлены, платформа пишет ячейкам ноль.
На пилоте верхняя категория row[].cell[].fmt упала с 63 900 до 55 855 фактов,
совпадение строк документа выросло с 16.7% до 17.5%.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Справочник был одним файлом на 273 строки, а полная таблица свойств стиля его
бы удвоила. Разделён по частоте обращения, как в meta-compile: в инструкции
маршрутная таблица «что нужно → какой файл».
reference/dsl-spec.md — верхний уровень, области, строки, ячейки
reference/styles.md — шрифты, стили, цвет, рамка, колонки
reference/format-properties.md — полный перечень свойств стиля
Описаны columnStyles и новая запись стиля: ключ = имя свойства как в выгрузке,
рамка пятью ключами, цвет в четырёх формах. Прежние align/valign/wrap работают
и дальше, но в описании их нет — иначе у модели появляется развилка.
Ограничения переписаны по факту: цвета, скрытие, отступ, посторонние рамки и
стили линий больше не теряются; зато честно названо то, что теряется до сих пор —
оформление строки помимо высоты.
Пример из спеки скомпилирован и проверен валидатором.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа иногда перечисляет колонку в раскладке с <formatIndex>0</formatIndex> —
колонка объявлена, формата у неё нет (4% макетов корпуса). Компилятор такие
опускал, декомпилятор их не видел, и элемент терялся целиком.
Выражается пустым значением в columnStyles. Для авторинга это бесполезно —
ни одна задача не звучит как «объяви колонку без свойств», — поэтому в описании
DSL записи нет: она нужна только чтобы раундтрип не терял байты.
На пилоте потери в категории colset[].col[].formatIndex упали с 39 до 35.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
У ячейки есть style, у строки rowStyle, у колонки не было ничего — при том что
колонка ссылается в ту же палитру <format> и несёт те же свойства. В корпусе
ERP колонки используют шрифт (3006 форматов), выравнивание, рамки, скрытие и
прочее; всё это терялось при раундтрипе.
Колонка получает такой же именованный стиль: columnStyles рядом с columnWidths,
ключи той же грамматики диапазонов ("1", "2-8", "5,7,9"), значение — имя из
styles. Внутри columnSets тот же ключ. Формат колонки собирается из ширины и
свойств стиля: запись в палитре одна.
Шрифт по умолчанию колонке не навязываем — формат колонки без оформления это
ровно <width>, как пишет платформа.
Попутно два дефекта декомпилятора: стиль колонки не учитывался при отсечении
неиспользуемых стилей и пропадал целиком, а набор из одних неприметных свойств
(отступ, защита) получал зарезервированное имя default и тоже терялся.
На пилоте потери в категории colset[].col[].formatIndex упали с 86 до 39.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Декомпилятор снимал из <format> только шрифт, четыре рамки, выравнивания,
перенос и форматную строку — остальные тридцать с лишним свойств терялись
молча. Компилятор их уже умеет, но до раундтрипа они не доезжали.
Теперь формат читается целиком по общей таблице типов — той же, что у
компилятора: рамки разворачиваются в описание линии из палитры, булевы и
целые приводятся по типу, цвет из web/win-палитры возвращается в привычную
запись (префикс узла разрешается в URI пространства имён).
Имя стиля осталось читаемой меткой по заметным свойствам: различает стили
ключ, имя лишь помогает автору ориентироваться.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Снэпшоты mxl-decompile/mxl-info/mxl-validate строятся preRun-прогоном
mxl-compile. Дрейф одного вида: четыре одинаковые стороны рамки стали
одним <border>.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Стиль описывал 7 свойств из 47, которые платформа хранит в <format>. Не было
самого частого тега корпуса — backColor (1.7 млн вхождений), а также textColor,
textPlacement, protection, hidden, indent, borderColor, textOrientation и хвоста.
Теперь ключ стиля — имя тега платформы, без исключений: помнить, какие ключи
названы по-своему, больше не нужно. Прежние align/valign/wrap продолжают
работать молча, как синонимы; там же CSS-имена (background, color,
border-bottom) и русские имена свойств. Таблицы типов значений и допустимых
значений перечислений сняты с корпуса, а не выписаны на глаз.
Рамка: пять плоских ключей (border и четыре стороны) со значением
{ style, width } либо строкой стиля; линия регистрируется в палитре <line>,
которая раньше знала только «тонкую» и «толстую» Solid. Четыре одинаковые
стороны сворачиваются в один <border> — правило проверено на корпусе:
70 265 свёрнутых форматов против 36 783 записанных по сторонам, и среди
вторых нет ни одного с четырьмя совпадающими значениями.
Цвет — нотация самой платформы: #RRGGBB, style:Имя, web:Имя, win:Имя. Первые
две пишутся дословно (префикс style объявлен в корне документа), для web и win
платформа дописывает объявление xmlns прямо на узел — делаем так же.
containsValue / valueType / controlType намеренно не заведены: это свойства
конкретной ячейки, а стиль — сущность общая, один на многие ячейки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Формат был фиксированной записью из 12 полей, выписанной руками в четырёх
местах: ключ дедупликации, набор свойств, резолв стиля и эмиссия палитры.
Добавление свойства стоило восьми правок в двух портах, а свойств у формата
в выгрузке 47.
Теперь формат — набор «тег платформы → значение», а порядок эмиссии задаёт
один канонический список тегов. Список снят с корпуса ERP: 766 960 форматов,
ни один не нарушает эту последовательность.
Вывод не меняется: пилот из 40 макетов скомпилировался побайтово так же, как
до правки, обоими портами.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Снэпшоты mxl-decompile/mxl-info/mxl-validate строятся preRun-прогоном
mxl-compile, поэтому дрейфуют вместе с ним. Дрейф ровно двух видов:
убран избыточный <i> и схлопнуты пустые строки в indexTo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Две правки эмиссии, обе выведены из корпуса erp_8.3.24 (10924 макета,
15.2 млн ячеек) и проверены на нём же без единого контрпримера.
Номер колонки <i> платформа пишет только при разрыве последовательности:
подряд идущая ячейка его не несёт, читатель ведёт счётчик сам. Мы писали
всегда — из 1.2 млн записанных платформой номеров ни один не избыточен.
Подряд идущие одинаковые ПУСТЫЕ строки платформа хранит одним rowsItem
с indexTo; непустые не схлопывает, даже когда они совпадают (98153 таких
случая в корпусе). Мы не эмитили indexTo вовсе.
На пилоте (40 макетов) доля строк документа, совпавших с оригиналом
байт в байт, выросла с 0.3% до 16.7%; все 9 диапазонов indexTo оригинала
воспроизведены точно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Write-Error оборачивает сообщение в ErrorRecord: приписывает имя скрипта,
добавляет хвост CategoryInfo и ломает текст по ширине окна консоли. Длинные
сообщения приезжали разорванными посреди слова, и по ним нельзя было
ни искать подстроку, ни читать их глазами.
Остальные четырнадцать мест в файле уже писали через Console.Error —
приведены оставшиеся шесть. Тексты сообщений не менялись.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
«Внутри конфигурации набор обычно одинаков» — тот же вывод из замеров, только
без числа: проверить его по месту нельзя. Совет смотреть соседние макеты толкает
на лишний обход при авторинге с нуля, а упоминание, что платформа разрешает
необъявленные языки, читается как приглашение их указывать.
Осталось свойство самого ключа: он ни на что в конфигурации не смотрит.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Проценты по типовым конфигурациям модель может принять за правило, а объяснение,
почему набор языков нельзя вывести из текстов, ей для применения не нужно.
Осталось то, что помогает выбрать значение: языки текстов и языки конфигурации
совпадать не обязаны, ориентир — соседние макеты той же конфигурации.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Строка в text/template разворачивается по одному элементу на каждый язык из
документного ключа textLanguages (по умолчанию только ru). Декомпилятор собирает
набор языков по всем текстам макета и пишет строкой текст, одинаковый на всём
наборе; различающийся остаётся объектом.
Языки текстов и языки, объявленные в конфигурации, — разные вещи: типовые ERP
объявляют один ru, а тексты хранят под ru и en. Набор постоянен внутри
конфигурации, поэтому он документный, а не вычисляемый: к моменту компиляции
тексты уже свёрнуты в строки.
Заодно: эмиссия tl проверяет НАЛИЧИЕ ключа text/template, а не истинность —
пустая строка это текст, платформа такие ячейки пишет с пустым tl, а по
истинности он терялся.
Проверено на пилоте (40 макетов ERP): скомпилированный XML совпадает с прошлым
прогоном байт в байт обоими рантаймами, объём JSON −47%.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Компилятор давно принимал короткую форму, а декомпилятор всегда писал самую
развёрнутую. Теперь он выбирает самую короткую из применимых, как это делает
skd-decompile.
Короткая запись стала свойством СПИСКА ЯЧЕЕК, а не строки: cells принимает
позиционный список, поэтому строка с height или rowStyle тоже может им пользоваться.
Раньше наличие свойств строки лишало её короткой записи — искусственная связка.
Если же своих свойств у строки нет, она пишется просто массивом, без ключа cells.
Позиционный список опознаётся по наличию элемента-строки или null; список из одних
объектов читается как обычный — для простой строки обе трактовки дают один результат.
Три дефекта, вскрытых проверкой на потери (каждый раз XML переставал совпадать с
прежним, и это приводило к причине):
- компилятор двигал позицию на единицу за элемент, поэтому объектный элемент со
span: 3 занимал одну позицию вместо трёх и всё правее него уезжало влево. У маркеров
">" этого не было — каждый съедает свою позицию;
- проход «удалить неиспользуемые стили» не заглядывал в строки-массивы: у массива нет
свойства cells, и стиль, использованный только внутри такой строки, вырезался как
неиспользуемый;
- в py тот же проход падал на позиционном списке — "style" in c для None бросает
TypeError, а для строки делает подстрочный поиск.
Плюс ловушка PowerShell: += разворачивает вложенный массив, и строка-массив
расплющивалась в список строк — нужна запятая.
Проверено: XML на всех 40 макетах пилота совпал с прежним БАЙТ В БАЙТ, то есть
сокращение без потерь. Объём декомпилированного JSON меньше на 11%. Тесты 54/54 на
обоих рантаймах, гарды зелёные.
В WORKFLOW уточнена находка про паритет портов: декомпиляторы расходятся по JSON на
33 макетах из 40, а компиляторы на одном и том же входе — всего на 2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Одно условие делает две вещи.
Сокращение: у 56% ячеек (19 737 из 35 190) стоял "style": "default", хотя компилятор
и так подставляет default, когда ключа нет и у строки нет rowStyle. Чистый шум;
объём декомпилированного JSON на пилоте упал на 7%.
Починка: при ЗАДАННОМ rowStyle ячейка со стилем default, которая его не наследует,
теряла стиль вовсе — условие содержало -not rowStyleName, — и при обратной сборке
такая ячейка наследовала rowStyle вместо своего умолчания.
Проверка на потери: из 40 макетов пилота 23 собрались байт в байт как прежде. У 17
XML изменился, и это разобрано поячеечно: во всех 181 разошедшейся ячейке НИ ОДНА
версия не совпадает с оригиналом — там backColor, фон ячейки, который DSL не
поддерживает вовсе (числится в ограничениях). То есть правка переключает между двумя
одинаково неверными отображениями неподдерживаемой конструкции, не улучшая и не
ухудшая совпадение: фактов, совпавших с оригиналом, было 8183 и осталось 8183.
Проверено: тесты 54/54 на обоих рантаймах. Дрейф снэпшота один и ожидаемый — из
back.json ушёл "style": "default".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа хранит текст ячейки по элементу на язык. Декомпилятор брал ПЕРВЫЙ и терял
остальное, компилятор писал всегда ru захардкоженно. В корпусе ERP текст лежит и под
ru, и под en у 10 730 макетов из 10 924 — то есть терялось у 98%.
Форма взята готовая — конвенция ML-значений из docs/meta-dsl-spec.md §4.4, по которой
уже живут synonym, tooltip и title в метаданных и формах: строка означает русский
текст, объект даёт по надписи на язык в порядке ключей. Новых понятий не вводили,
ключи те же (text и template).
Документный ключ для языков НЕ заводили, и это измерение, а не экономия: блок
languageSettings мы и так эмитим байт в байт как платформа (currentLanguage ru,
defaultLanguage ru, один languageInfo). Отклонения редки — 8 макетов ERP с
currentLanguage en, 3 без него, 157 с объявленной парой ru+en, всего около 1,6%.
Заодно это снимает развилку из разбора PR #62: список языков компилятор дописывать
не должен, платформа его не дописывает.
Эффект на пилоте: категория row[].cell[].tl упала с 47 252 до 12 488 — минус 74%,
самое крупное улучшение кампании. Остаток частично классифицирован: 348 строк — ячейки
с ПУСТЫМ <tl> у оригинала, которых мы не эмитим вовсе; полностью разобрать мешает
обрезка диффа по 400 строк на объект.
Снэпшоты не дрейфанули: кейсы пишут текст строкой, и вывод для неё прежний.
Проверено: тесты 54/54 на обоих рантаймах, verify-snapshots 24/24, вывод портов
совпадает байт в байт и в компиляции, и в декомпиляции.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа держит для неоформленного макета ОДИН формат — ширину колонки, он же
defaultFormatIndex, и все ячейки указывают на него. Компилятор заводил каждой ячейке
собственный формат с <font>0</font>, где ноль означает «шрифт не задан», то есть
формат был пуст по смыслу и отличался от умолчания только своим существованием.
Теперь ячейка, у которой не задано ничего (шрифт умолчательный, нет рамок,
выравнивания, переноса, типа заполнения и формата числа), ссылается на
defaultFormatIndex. На минимальном макете палитра сократилась с двух форматов до
одного и совпала по форме с платформенной.
На корпусе правка закрывает случай неоформленной ячейки начисто: на макете
АтрибВыгрузкиXML2015Кв1 расхождения по формату исчезли полностью, блок диффа
сократился с 75 строк до 40, и всё оставшееся — многоязычный текст. В агрегате по
пилоту это 497 строк из 64 865: остальное держат макеты, где ячейки реально
оформлены, и там расходится состав самих форматов — отдельная категория.
Дрейф 18 снэпшотов разобран построчно, посторонних строк нет: 170 сменившихся
индексов <f>, 36 границ <format> (палитра стала короче), 18 удалённых <font>.
Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Идентификатор колоночной раскладки выводится из имени детерминированно — это
требование, а не оптимизация: случайный давал бы другой файл при каждой компиляции,
снэпшоты не совпадали бы сами с собой, а повторная сборка того же определения
порождала бы диф.
Но выводился он неправильно: сырой хэш форматировался как UUID, без битов версии и
варианта. В выдаваемом значении версия оказывалась 7, вариант 1 — таких не бывает,
то есть формально это был не UUID, а шестнадцатеричная строка нужной формы. Платформа
такое принимает (проверено сертификацией), но упереться в валидатор мы могли в любой
момент — ровно того же сорта риск, что сертификация уже ловила в этой ветке.
Теперь это штатный UUID версии 3 (имя + MD5, RFC 4122): для задачи «вывести
идентификатор из имени» существует именно он. Детерминированность сохранена, оба
порта дают одинаковое значение.
Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В ожиданиях column-sets стоит конкретный идентификатор раскладки, и по кейсу
не видно, откуда он взялся. Имя кейса теперь говорит, что id выводится из
имени детерминированно и что ожидаемое значение — md5 от имени раскладки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Две правки по корпусу и по 1С-сертификации.
fillType=Text платформа практически не пишет: на выборке корпуса 344 981 текстовая
ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType — наличие <tl> и так
означает текст. Parameter и Template платформа пишет, их оставляем. Правило то же,
что и везде: эмитим то, что эмитит платформа, а не то, что верно в рантайме.
Идентификатор колоночной раскладки платформа хранит как UUID и другой не принимает —
макет с <id>узкая</id> она отвергала целиком. Имя, похожее на UUID (то есть пришедшее
декомпиляцией), проходит насквозь, иначе поехал бы раундтрип; читаемое имя автора
превращается в UUID ДЕТЕРМИНИРОВАННО, из хэша имени, чтобы повторная компиляция
давала тот же файл. Оба порта дают одинаковый хэш.
Дефект с идентификатором нашла 1С-сертификация: ни юнит-кейсы, ни раундтрип по
корпусу его не видели — там идентификаторы приходят из исходных макетов и уже
являются UUID.
Дрейф 33 снэпшотов разобран построчно, посторонних строк нет: 116 сменившихся
индексов формата, 49 удалённых fillType, 43 перестановки свойств внутри палитры,
16 границ <format> (палитра стала короче).
Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.
Замечание на будущее: на корпусе эта правка снимает лишь 170 расхождений из 64 865
в категории формата ячейки. Основная причина другая — платформа даёт неоформленной
ячейке ссылку на формат по умолчанию, а компилятор заводит ей собственный формат
с <font>. Это отдельный заход, он сдвинет снэпшоты повторно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Раскладка адресуется именем из columnSets. Инлайновой формы в этом DSL нет ни у
чего: style у ячейки и font внутри стиля тоже только имена — именованные сущности
объявляются один раз и всюду адресуются по имени. Объектная форма стала бы второй
формой одного и создала бы асимметрию «раскладку инлайнить можно, а стиль нельзя».
Объект в columnSet и раньше не портил вывод — навык падал с «Unknown 'columnSet'»,
подставляя сериализованный объект. Но сериализация у портов разная (@{columns=5}
против {'columns': 5}), то есть сообщения расходились. Теперь это отдельная проверка
с текстом, одинаковым в обоих портах и прямо называющим правило.
Кейсы: column-sets (позитивный, ссылка и объявление доезжают до XML) и
error-columnset-inline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>