Неизвестный тип объекта обрабатывался веткой «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>