help-add терял текст справки. Отказ смотрит только на Help.xml, а страница может
пережить его (удалённый дескриптор, частичная выгрузка, справка, сделанная руками):
тогда запись шла безусловно и стирала содержимое с кодом 0 и рапортом [OK].
Теперь существующая страница сохраняется, создаётся только дескриптор — та же
защита, что уже стоит в template-add. Прежняя оценка «в help-add потери данных нет»
была неверной: она опиралась на чтение кода, а не на замер.
Код языка: регулярка пропускала имена устройств Windows. При -Lang nul py-порт
молча писал <Page>nul</Page> и пустой каталог с кодом 0 (страница уходила в NUL),
а PS падал исключением — то есть порты ещё и расходились. Якоря \A…\z вместо ^…$:
последние в обоих языках допускают перевод строки в конце.
Проверка вынесена в Test-LangCode / is_valid_lang и внесена в реестр
check-inline-drift: inline-блок гард не видел, а копий у него две.
Плюс мелочи оттуда же: мёртвый дизъюнкт в $pageExists убран; полумигрированное
дерево (дескриптор есть, старый Ext/Template.html остался рядом) теперь получает
предупреждение, а не молчание; существование страницы в py сверяется без учёта
регистра — иначе на Linux -Lang RU писал бы вторую страницу мимо дескриптора.
Четыре кейса на каждый сценарий. Существующие эталоны не сдвинулись.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ревью вскрыло два дефекта во вчерашней ветке добавления языка — оба
воспроизведены руками.
1. Миграция старой раскладки шла ДО проверки дубля языка. При дефолтном -Lang ru
(самый вероятный способ повторно позвать навык на старом макете) Template.html
уже был перенесён, затем срабатывал отказ «страница ru уже существует» — и
дескриптор не записывался вовсе. Макет оставался разобранным: страница есть,
дескриптора нет, платформа такую раскладку снова молча игнорирует. Печатавшийся
при этом [WARN] «создан дескриптор» был неправдой.
2. Файл страницы писался безусловно, а проверялся только список <Page> в
дескрипторе. При рассинхроне (страница на диске есть, в дескрипторе нет —
в том числе после дефекта 1) содержимое затиралось пустым скелетом с кодом 0.
Теперь ветка сначала разбирает состояние целиком и только потом пишет, файл
страницы не перезаписывается никогда: существующая страница подхватывается, в
дескриптор дописывается <Page>. Старая раскладка с -Lang ru — это не коллизия,
а ровно тот случай, ради которого миграция и нужна. Состояние, где есть и
Template.html, и Template/ru.html, навык не разруливает сам: отказ до изменений.
Плюс валидация -Lang: код языка идёт и в текст XML, и в имя файла, поэтому пустое
значение давало файл «.html» с пустым <Page></Page>, а разделитель пути — запись
мимо Ext/Template. Оба отказа платформы были бы тихими.
Четыре новых кейса закрывают каждый сценарий; паритет портов сверен побайтово.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Повторный вызов на существующем макете безусловно отказывал, поэтому второй язык
приходилось добавлять руками — дописывать <Page> в дескриптор и заводить файл
страницы. Случай не редкий: в выгрузке ERP 54 макета из 70 двуязычные.
Теперь при -TemplateType HTML существующий макет — повод добавить страницу, а не
отказать. Порядок <Page> — по коду языка (в ERP так во всех 54). Версия формата
берётся из самого дескриптора, чтобы правка чужой выгрузки не меняла формат.
Метаданные макета и ChildObjects не трогаются: перезапись сменила бы UUID.
Отказ остался там, где он по делу: тот же язык повторно, нехтмловый макет под тем же
именем, любой не-HTML тип.
Макет в старой раскладке (Ext/Template.html) мигрируется с громким [WARN]: платформа
такую раскладку игнорирует, то есть состояние и так нерабочее, а создать её могла
только версия навыка без -Lang — значит это страница на языке по умолчанию.
Проверено на 8.3.27.1859: двуязычный макет доходит до базы и возвращается обратно
байт в байт — и дескриптор, и обе страницы. Попутно измерено: страницу на языке,
не объявленном в Languages/, платформа принимает — поэтому состав языков не проверяем.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Навык создавал Templates/<Макет>/Ext/Template.html. Платформа такой файл молча
игнорирует: загрузка проходит без ошибок и предупреждений, а макет в базе пустой.
HTML-макет платформа хранит парой, как справку: дескриптор Ext/Template.xml со
списком страниц и сама страница Ext/Template/<язык>.html (картинки — в _files/).
Раскладка снята с выгрузки acc_8.3.27: 136 HTML-макетов из 136, дескриптор во всех
байт в байт одинаков. Шапка страницы — в виде редактора платформы (одной строкой,
парный </meta>), чтобы первое сохранение в Конфигураторе не давало диффа.
Язык страницы задаётся параметром -Lang (дефолт ru) — как в help-add.
Вывод навыка теперь различает содержимое и дескриптор: «Содержимое» указывает на
страницу, иначе правка ушла бы в дескриптор.
Проверено на 8.3.27.1859: LoadConfigFromFiles + UpdateDBCfg + обратная выгрузка —
текст макета доходит до базы и возвращается байт в байт, дескриптор тоже.
Контроль — тот же макет в старой раскладке: в выгрузке из базы тела нет вовсе.
EPF: epf-build → epf-dump — раскладка возвращается с содержимым.
Диагноз и раскладка — из PR #98.
Co-Authored-By: Roman Syuzyov <rsyuzyov@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Файл .fixture-stamp лежал внутри каталога фикстуры, а фикстура копируется в
рабочий каталог кейса целиком — и отпечаток попал в 11 снэпшотов. Перенесён в
<cache>/<setup>.stamp, из снэпшотов удалён.
663/663 ps1, 660/663 py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Каталог фикстуры кэшировался навсегда: правка cf-init до него не доходила,
снэпшоты записывались со старой фикстурой, и расхождение всплывало только на
машине с пустым кэшем. Поймано прогоном на маке — там фикстура собралась заново
и принесла TextToSpeech, которого нет в снэпшотах, записанных на Windows.
Теперь рядом с фикстурой лежит отпечаток: аргументы плюс хэши обоих портов
cf-init. Не совпал — каталог пересобирается.
Снэпшоты кейсов 2.21 перезаписаны со свежей фикстурой (дрейф — только
TextToSpeech). У mxl-compile снэпшот кейса снят через noSnapshot: у навыка
normalizeUuids=false, поэтому UUID фикстуры дрожали бы при каждой пересборке,
а предмет кейса проверяет fileContains.
663/663 ps1, 660/663 py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Фикстура empty-config-221 (cf-init -FormatVersion 2.21) и по кейсу на навык:
cf-init, meta-compile, meta-edit, form-add, form-compile, template-add,
mxl-compile, subsystem-compile, subsystem-edit, role-compile, xdto-compile,
cfe-init, cfe-borrow, epf-init, erf-init.
Проверки через expect.fileContains по сырым байтам, а не только снэпшотом:
позиция xmlns:pal (после lf, перед style) и его отсутствие там, где платформа
его не пишет (Rights.xml роли, Ext/ClientApplicationInterface.xml).
663/663 ps1, 660/663 py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Из ~46 навыков-эмиттеров XML восемь не имели ни одной проверки `preserves`:
form-edit, form-remove, meta-remove, skd-edit, template-remove, xdto-edit,
support-edit, cfe-patch-method. Шесть из них правились в предыдущем коммите,
и канон у них держался на том, что правка была механической, а не на проверке.
Ожидания писались ПО КАНОНУ, а не по текущему поведению — и это сразу вскрыло
три дефекта:
1. form-remove очищал слот формы через InnerText="" / text="" — получалась
пустая пара <DefaultObjectForm></DefaultObjectForm>. Платформа пустых пар
не пишет (0 на 476 942 XML). Теперь IsEmpty / text=None.
2. Опустевший <ChildObjects> оставался парой, разнесённой по строкам, —
в PS-порту form-remove и template-remove. Корпус: 1394 самозакрывающихся
<ChildObjects/> на acc+erp, пустых пар 0 в обеих формах.
3. template-add писал пустой макет парой <SpreadsheetDocument></...>.
Во всей выгрузке acc_8.3.27 (65 040 XML) многострочных пустых пар нет
ни для одного тега.
Заодно усилена сама проверка noEmptyPairs: она ловила только СМЕЖНЫЕ теги,
поэтому дефект №2 проходил мимо неё. Добавлен вариант с переводом строки
внутри; дискриминатором служит сам перевод строки — значащий пробельный
текст-узел (<xr:FillValue xsi:type="xs:string"> </xr:FillValue>) его не
содержит и под проверку не попадает.
support-edit покрыт частично (BOM у ParentConfigurations.bin — проверено, что
платформа пишет его с BOM во всех трёх выгрузках), cfe-patch-method — BOM+EOL
у .bsl: хвостовой перевод строки у модулей неканоничен (1235 с ним, 766 без),
поэтому не утверждается.
Регресс: 647/647 ps1, 644/647 py (3 skipped). Эталоны переснятые: три места,
каждое — ровно ожидаемая пара строк.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Тот же writer без newline="", что чинился в form-add.py, остался ещё в четырёх
py-портах, и пишут они не модули, а 1С XML: заимствованные объекты и метаданные
формы (cfe-borrow), Template.xml и XML-макеты (template-add), Help.xml (help-add),
вновь создаваемый CommandInterface.xml (interface-edit).
Контент там собирается литералами с \n, поэтому текстовый режим Python давал CRLF
на Windows — «случайно правильно» — и LF на macOS, ломая канон #57 ровно там, где он
только что установлен. Проверено на маке ДО фикса: cfe-borrow 0 CRLF + 33 одиночных
LF, template-add 0 + 2, interface-edit 0 + 5. Порчи \r\r\n не было, но это везение:
подай такой writer CRLF-контент — каждая строка стала бы \r\r\n.
Почему не поймали раньше: аудит гонялся на Windows, где текстовый режим даёт канон;
снэпшоты нормализуют EOL; байтовый preserves ни разу не проверялся на маке.
Разделены два пути, которые я сперва смешал в cfe-borrow:
- save_xml_file — канон (CRLF, без хвоста) для файлов, которые СОЗДАЁМ;
- save_text_bom — пишет как есть, для файлов, которые ПРАВИМ: переводы строк уже
пришли из самого файла и менять их нельзя (контракт #44/#46/#47). Туда же
добавлено чтение с newline="" — иначе CRLF терялся ещё на входе.
interface-edit чинится и в PS-порте: ветка -CreateIfMissing давала смешанный EOL
(3 CRLF + 2 одиночных LF) уже на Windows. Аудит #57 её пропустил, потому что
фильтровал файлы по расширению .xml, а кейс создаёт файл с именем без расширения.
Канон, как выяснилось, зависит от типа артефакта: XML — CRLF, текстовый макет —
CRLF, а HTML-макет платформа хранит с LF (корпус: 399 LF из 400). Поэтому HTML
через канон-writer НЕ идёт.
preserves добавлен четырём навыкам (у них его не было вовсе) + отдельно на Help.xml.
Именно прогон на маке и даёт покрытие: на Windows эти кейсы зелены и без фикса.
Windows 641/641 обоими портами, мак 590/0/51, дрейфа снэпшотов нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
.NET XmlWriter пишет `<a />`, а Конфигуратор — `<a/>`. Поскольку Save переписывает
файл целиком, meta-edit при добавлении одного реквизита переводил в пробельную форму
все пустые теги документа. Дефект только у PS-порта: lxml (45 из 49 py-портов) уже
пишет плотно, то есть порты расходились побайтово.
Канон измерен, а не предположен: чистая выгрузка пустой ИБ на Windows и macOS плюс
сплошной скан 8 выгрузок в cfsrc — 476 943 XML, 21 294 119 самозакрывающихся тегов,
пробельных 0. Форма не зависит от ОС, версии платформы (8.3.20-8.5) и наличия
атрибутов. Правило уже было реализовано в skd-edit — оттуда и взято.
Замена безопасна доказуемо: .NET экранирует `>` как `>` и в тексте, и в атрибутах,
поэтому ` />` после Save — только конец тега. Лазейки (CDATA, комментарии) в
1С-метаданных не встречаются — 0 из 476 943 файлов; гард на них всё равно стоит.
Одиннадцать скриптов писали прямо в FileStream без пост-обработки — переведены на
MemoryStream, что заодно чинит `encoding="utf-8"` строчными (335 файлов в снэпшотах).
В cfe-borrow правится и сборка Form.xml: куски берутся из OuterXml, а он спацовывает
так же, как XmlWriter.
Дрейф снэпшотов: 12 838 строк тегов + 335 деклараций, содержательных изменений ноль.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Приводим авторский контент (.ps1/.psm1/.py/.mjs/.md/.json, пин .bsl)
к единому LF и закрепляем политикой в .gitattributes. Инструмент правки
всегда пишет LF, поэтому единый LF убирает EOL-шум в диффах, ложные
срабатывания blame и налог на ручную синхронизацию CRLF-файлов.
BOM на .ps1 сохранён (git с eol=lf меняет только CR<->LF, BOM не трогает).
Данные 1С (*.xml) и бинарники под нормализацию не берём.
Гейт: PS-порт 459/459, Python-порт 459/459, web-test E2E 22/22 (с пересборкой стенда).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Регрессионная защита гарда: по одному expectError-кейсу guard-deny на
каждый из 13 размноженных мутаторов (раньше committed-тесты были только
у 3 пилотных). Ловит случайное удаление/поломку guard-вызова в будущем.
Фикстуры on-support (рукотворный bin: корень/объект f1=0, плюс элемент
f1=0 для edit-existing навыков — форма 4444, макет 5555, подсистема 6666).
Структура под конвенцию каждого навбыка: owner/root для add/compile/edit
конфигурации; плоский Locked/Ext для help-add (EPF-стиль).
Заодно исправлен пред-существующий баг help-add Detect-FormatVersion
(v1.5→v1.6, оба порта): Substring(0, byteLength) падал на кириллическом
Configuration.xml (байт>символов). Теперь Substring по длине строки;
фикстура help-add кириллическая — регрессия фикса покрыта тестом.
Все 13 guard-кейсов зелёные на PowerShell и Python; deny через exit≠0 +
stderr "support-guard". Существующие кейсы не затронуты.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add 195 new test cases covering examples from SKILL.md, edge cases,
and parameter combinations. Create _skill.json for form-edit, skd-edit,
subsystem-edit. Add fixtures for negative validate cases. Fix
normalizeUuids in meta-validate/meta-info configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>