Значение xr:Abs и имя элемента с разделителем пути или «..» пропускаются
с предупреждением: иначе чтение и запись уходили за пределы Items, а порты
расходились на имени со «/». Источник-каталог больше не принимается за файл
картинки в PS-порте (как и в py).
Кейсы: предупреждение о картинке, которой нет в источнике; повторное
заимствование проверяет и вывод «Preserved existing».
Co-Authored-By: AndromanPro <AndromanPro@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Form.xml заимствованной формы ссылается на встроенные картинки элементов
(<xr:Abs>Picture.png</xr:Abs>), а сами файлы Ext/Form/Items/<Элемент>/<Файл>
в расширение не попадали — платформа отвергала загрузку: «Файл не найден».
Поведение сверено с Конфигуратором (8.3.27, формы УТ):
- Picture кнопок, RowsPicture/HeaderPicture/ValuesPicture таблиц и полей
картинки остаются в обеих частях формы, файлы копируются байт в байт;
- у PictureDecoration картинка любого вида выбрасывается из обеих частей,
без файла и без заимствования общей картинки.
Навык копирует ровно те файлы, на которые ссылается итоговый скелет формы
(ссылка xr:Abs -> Items/<имя владельца>/<файл>, точно на 666 ссылках корпуса).
Уже лежащий в расширении файл не перезаписывается. Оба порта, v1.38.
Раннер тестов пишет и сверяет двоичные файлы эталона побайтно: текстовый путь
через UTF-8 портил картинку, и сверка испорченного с испорченным проходила.
Проблема и первое решение — PR #102.
Co-Authored-By: AndromanPro <AndromanPro@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
add/set на файле без элемента уже отказывали («файл не из выгрузки платформы?»),
а remove печатал WARN и выходил с кодом 0. У заимствованного объекта отсутствие
элемента по-прежнему значит «удалять нечего» — предупреждение.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ps1 сопоставлял ключ свойства без учёта регистра, но при создании элемента писал
тег как подан: registerRecords у заимствованного документа давал <registerRecords>,
и meta-validate его пропускал. py сравнивал точно и на том же входе отказывал.
Ключ свойства объекта и дочернего элемента теперь сводится к каноническому имени
(ключи веток modify — name/type/synonym… — впереди списка свойств), дальше в обоих
портах только оно. Попутно: py при переименовании сравнивал синоним с разбивкой
старого имени с учётом регистра и не обновлял авто-синоним «ИНН»; сравнение теперь
без учёта регистра, как в ps1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
modify.properties создавал отсутствующий элемент до Set-ComplexProperty, и решение
Get-ListPropertyElement обходилось: пустой список дописывал <RegisterRecords/>
заимствованному объекту, а у обычного объекта без элемента не было отказа.
Свойство-список теперь уходит в Set-ComplexProperty до create-if-missing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
WARN с кодом 0 означал «не смог»: в пакетном прогоне это выглядело как успех.
Теперь предупреждение остаётся только там, где нужное уже есть (элемент уже
добавлен, удалять нечего, позиция after/before не найдена — добавлено в конец).
Остальное — ошибка в stderr и код 1: неизвестный ключ или операция, неверный
формат, недопустимый у типа ребёнок, изменение несуществующего элемента, формы и
макеты (их делают form-add/template-add).
Отказ атомарен: побочные файлы (таблица внешнего источника, модуль команды,
предопределённые) пишутся после основного XML, так что отказ посреди определения
не оставляет на диске ни одного изменения.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
У заимствованного объекта расширения в Properties только изменённые свойства.
add-registerRecord и прочие add-*/set-* над списками не находили элемент, печатали
WARN, файл не меняли и выходили с кодом 0.
- Движения заимствованного документа: отсутствующий RegisterRecords создаётся.
- Прочие списки у заимствованного объекта — отказ. Проверено на 8.3.27: Owners и
RegisteredDocuments платформа контролирует на равенство основной конфигурации
(расширение не применяется), BasedOn/InputByString/DataLockFields и движения
последовательности молча выбрасывает при загрузке.
- Свойство, которого у типа объекта нет (движения у справочника), — отказ; раньше
modify.properties дописывал его, и meta-validate это пропускал.
- У обычного объекта отсутствие элемента — отказ вместо WARN.
- reference/properties.md: типы объектов в таблице — по выгрузкам ERP, БП, УТ, УНФ.
- verify-snapshots: двухэтапная загрузка и по params.extensionPath кейса.
Co-Authored-By: Roman Syuzyov <rsyuzyov@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Платформа выбрасывает Command из внешней обработки/отчёта при сборке
(«не является подчиненным»), а Ext/ManagerModule.bsl — вовсе без
сообщения; epf-build при этом сообщает об успехе. Цепочка молча теряла
написанное: meta-edit добавлял команду, epf-validate её пропускал.
meta-edit: внешние типы внесены в таблицу допустимых детей — команду
не добавляет, предупреждает. epf-validate: Command и ManagerModule.bsl —
ошибки с пояснением.
Refs #108
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Автопроверка после правки жёстко звала meta-validate, который
ExternalDataProcessor/ExternalReport не знает: корректная правка
заканчивалась «Unrecognized metadata type». Валидатор теперь выбирается
по типу объекта. В py-порту stdout сбрасывается перед запуском
валидатора — его вывод больше не обгоняет строки meta-edit.
verify-snapshots: кейс meta-edit с setup none собирается epf-build,
а не грузится пустой конфигурацией, где обработку платформа не видит.
Fixes#108
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Перезапись брала адрес только из первой строки Listen: при привязке
к loopback по обоим стекам (127.0.0.1 + [::1]) повторная публикация
молча выбрасывала IPv6-строку. Теперь сохраняются все адреса блока
(повторы схлопываются), меняется только порт; URL — по первому.
Refs #107
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
web-info брал первое число строки Listen: при `Listen 127.0.0.1:19081`
порт выходил 127, URL и проверка TCP были ложными. Теперь разбирается
`Listen [host:]port [proto]` (включая IPv6 в скобках), сначала в своём
глобальном блоке; явный адрес идёт в URL и в пробу порта.
web-publish при перезаписи глобального блока сохраняет адрес привязки,
вписанный вручную, — повторная публикация больше не открывает loopback
на все интерфейсы; URL в выводе строятся по нему. Стандартный Listen
комментируется целой строкой (раньше `Listen 0.0.0.0:80` превращался
в мусор, а `Listen [::]:80` оставался активным).
Refs #107
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Списки объектов и отчёт db-repo лежали в общем временном каталоге под
постоянными именами: параллельный запуск молча подменял список, по которому
модель перевыгружает устаревшие исходники, а при отказе платформы навык мог
напечатать отчёт предыдущего запуска. Имена получают случайный суффикс —
путь и так печатается в вывод.
Логи /Out базы-заглушки переехали в каталог самой базы: он уникален на запуск
и удаляется вместе с ней, чужой или устаревший лог в вывод не попадёт.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Временный каталог брался из $env:TEMP, которой вне Windows нет. Join-Path
падал на привязке параметра внутри try/finally без catch: try прерывался,
finally отрабатывал, и скрипт выходил с кодом 0 — платформа не запускалась,
постусловие не проверялось (#106).
- временный каталог — [IO.Path]::GetTempPath() (на Windows тот же путь);
- верхнеуровневый trap { … exit 1 } в 15 скриптах db-*/epf-*, запускающих
платформу: любая необработанная ошибка даёт код 1 и печатает место;
- уборка временного каталога в finally не падает на пустом пути;
- гард check-ps-portability.mjs держит оба правила.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Проверка исходников идёт в конфигурации-заглушке, где общих модулей не было:
обработка с кодом БСП не собиралась без -Checks off.
- stub-db-create -ConfigSrc: общие модули, к которым обращается код, получают
пустых двойников с флагами контекста из выгрузки; неизвестные имена не
угадываются
- epf-build -ConfigSrc, по умолчанию configSrc базы из .v8-project.json
- подсказки к «Переменная не определена (X)»: модуль не в том контексте /
модуля нет в configSrc / выгрузка не передана (с -ConfigSrc и -Checks off)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- убран несуществующий ТипКомандыСценарийВБезопасномРежиме(), добавлен
ТипКомандыЗагрузкаДанныхИзФайла() с шаблонами трёх процедур загрузки;
список типов помечен исчерпывающим, указана применимость к видам
- СозданиеСвязанныхОбъектов: серверный ВыполнитьКоманду с СозданныеОбъекты
(4 параметра) — также в epf-bsp-init
- клиентские обработчики: Печать для печатных форм, вариант для создания
связанных объектов
Refs #82
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANXwbMUhuRFSCsq2RgmTTm
Команда формы объявлена с действием, а процедуры в модуле не было —
epf-build проверяет исходники с -HandlersExistence и отменял сборку.
Обработчик вставляется в модуль формы; добавлена проверка, что он
пережил сборку и разборку.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANXwbMUhuRFSCsq2RgmTTm
Удалённый объект уходит из базы, только если в списке его владелец и в
составе владельца объекта уже нет; удалённый файл-часть (модуль, Ext/)
загрузка частями не удаляет никогда. Скрипт классифицирует удаления:
применимые — [note], остальные — [ВНИМАНИЕ] с причиной; если загружать
нечего, а неприменимые удаления есть — код 1. git diff с --no-renames,
чтобы старый путь переименования был виден. -AllExtensions отвергается
на обоих движках: частями грузится одно расширение за раз.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANXwbMUhuRFSCsq2RgmTTm
- -update без -force: при другой версии формата — отказ с подсказкой
про -Mode Full, а не молчаливая перезапись всего дерева в новом формате.
- Первая выгрузка режимом по умолчанию: в каталог без выгрузки (пусто,
только .git/README) — полностью; выгрузка без ConfigDumpInfo.xml — ошибка.
- ibcmd: Changes через --sync; Full в непустой каталог — через временный
каталог и копирование поверх, как пишет конфигуратор; ошибка копирования
даёт код 1. UpdateInfo с -AllExtensions отвергается, а не выгружает всё.
- py-порт печатает вывод ibcmd, как PS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANXwbMUhuRFSCsq2RgmTTm
Кейсы навыков, читающих изменения из git, строят репозиторий шагом
{ "git": [...] }. Общая реализация для runner.mjs и verify-snapshots.mjs
в tests/common/git-step.mjs — настройки машины на фикстуру не влияют.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANXwbMUhuRFSCsq2RgmTTm
Параметр звался СуммаДокумента и сравнивался с кодом справочника: имя пришло из кейса
реквизита, где оно нужно ради авто-вывода «Сумма документа», а здесь заголовок задан
явно и от имени не зависит. Теперь КодОрганизации — запрос читается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSyRNwSiuAAEQCLSCUGEzQ
Кейс сравнивал числовые литералы со строковыми параметрами
(«ГДЕ 1 = &ОрганизацииВЕТИС» при "type": "string") и обходился без таблицы.
На проверяемое поведение это не влияет — навык заголовки читает, а текст запроса
переносит строкой, и платформа при загрузке конфигурации запрос не разбирает
(verify-snapshots проходил и на прежнем варианте). Но кейс читают как образец,
поэтому запрос теперь нормальный: динамический список над справочником, строковые
параметры сравниваются со строковыми полями.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSyRNwSiuAAEQCLSCUGEzQ
Ветка параметра читала заголовок обычным Get-LangText, который схлопывает whitespace-only
в пустую строку; ветка реквизита давно читает его через Get-LangTextWS. Заголовок-пробел —
им гасят подпись — превращался в "" , а компилятор трактует пустую строку как отсутствие
ключа и подставляет авто-вывод из имени: " " молча становилось «Организации ВЕТИС».
Тот же класс, что и потерянный регистр, соседняя ветка той же функции.
В корпусе (13891 узел dcssch:title в четырёх типовых) платформа не пишет ни пустой,
ни whitespace-only заголовок, поэтому на разборе чужих выгрузок правка ничего не меняет —
она чинит наш собственный раундтрип compile → decompile → compile.
Кейс dl-parameter-title-case расширен вторым параметром с заголовком-пробелом; на исходной
версии падает в обоих портах. Снэпшоты приняты платформой (verify-snapshots, 3/3).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSyRNwSiuAAEQCLSCUGEzQ
Title-FromName/title_from_name продублирован в form-compile и form-decompile: компилятор
пишет заголовок, которого нет в DSL, а декомпилятор на этом же выводе решает, можно ли
заголовок опустить. В комментарии копия заявлена «ТОЧНЫМ зеркалом», но ничем не держалась.
Разъедутся копии — заголовок либо теряется, либо дублируется, и в обоих случаях текст на
форме меняется молча, без ошибки платформы.
Эталон варианта — form-compile: компилятор авторитетен, декомпилятор его зеркалит. Тело
копии приведено к эталону без смены поведения (разбиение строк в ps1; в py — выражение
вместо индексного цикла и `not parts` вместо `len(parts) == 0`; `p.isupper()` и
`p == p.upper()` расходятся только на частях без буквенных символов, а после двух
разделяющих регулярок такая часть недостижима).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSyRNwSiuAAEQCLSCUGEzQ
Заголовок реквизита формы и параметра динамического списка опускается, если равен
авто-выводу из имени: компилятор восстановит его сам. Сравнение было
регистронезависимым (в ps1 -eq/-ne, в py зеркалящий их _ps_ieq), поэтому
«Организации ВетИС» при имени ОрганизацииВЕТИС считался равным авто-выводу
«Организации ВЕТИС», выпадал из DSL, и обратная сборка молча меняла текст на форме.
Эталон сравнения — не PowerShell, а компилятор: он пишет заголовок как есть, значит
опускать можно только при побайтовом совпадении. В ps1 этого не даёт и -ceq — он
культурный, мягкий перенос и NFD-разложение для него ничего не весят, и порты
разошлись бы между собой. Сравнение идёт через [string]::Equals(..., Ordinal),
что и есть зеркало питоновского ==. _ps_ieq больше не нужен и удалён.
Два регресс-кейса: реквизит формы (регистр + невидимый мягкий перенос) и параметр
динамического списка. Оба падают на исходной версии в обоих портах.
Найдено на типовой конфигурации: Документ.ИсходящаяТранспортнаяОперацияВЕТИС.ФормаСписка.
Co-Authored-By: Шпаков Антон Александрович <shpakov.anton.job@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSyRNwSiuAAEQCLSCUGEzQ
В предыдущем коммите бампнулся только py-порт: ps1 остался v1.26 при py v1.27.
Правила «версия в обоих файлах» набор тестов не проверяет — расхождение видно
только глазами в шапке.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
-Lang брался как есть и шёл и в текст XML, и в имя файла страницы. Измерено на
обоих портах: `-Lang "../../beyond"` писал beyond.html двумя уровнями выше
каталога Ext/Help и клал <Page>../../beyond</Page> в дескриптор, а `-Lang ""`
давал скрытый файл «.html» и пустой <Page></Page>. Оба случая — код возврата 0
и рапорт об успехе, то есть отказ платформы был бы тихим.
Проверка — дословная копия той, что появилась в template-add: навыки автономны,
но формат «дескриптор + страница» у них общий, и расходиться копиям незачем.
Дефект не регрессия, он был до текущей ветки; замечен при ревью соседней правки.
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>
Скелет страницы писал самозакрывающиеся <meta …/> и <link …/>. Платформа пишет их
парными: на 400 страницах справки из выгрузки acc_8.3.27 — 400 раз </meta>, нулей нет.
Расхождение безвредно для загрузки, но даёт лишний дифф при первом сохранении
страницы в Конфигураторе.
Замечено при разборе раскладки HTML-макета: template-add берёт скелет отсюда же.
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>
Список частичной загрузки задаёт объекты метаданных: вместе с объектом платформа грузит
его дочерние объекты (формы, макеты) и файлы Ext/. Configuration.xml — объект
«Конфигурация», поэтому его наличие в списке даёт полную загрузку конфигурации, а не
перечисленных объектов. В db-load-git он попадает в список из диффа сам, без участия
пользователя.
Замерено на обоих движках (1cv8 и ibcmd): правило одинаково.
- SKILL.md обоих навыков: строка про цену Configuration.xml в списке;
- хинт в выводе обеих веток движка;
- два кейса на срабатывание и на отсутствие ложного срабатывания.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кейс на `setup: external:` платформой не проверяется никогда — решение об этом
стояло в Step 7, а Step 0 до него успевал скопировать всю выгрузку типовой
конфигурации в рабочий каталог. Результат копирования тут же выбрасывался:
ни один шаг между Step 0 и Step 7 на вердикт для таких кейсов не влияет.
Цена была видна на глаз: 10 кейсов real-* у meta-info занимали 166-235 секунд
каждый, то есть около 32 минут чистого копирования ERP и БП. Прогон по навыку
стал 2 мин 53 с против ~35 минут при том же результате: 36 прошли, 0 упало,
те же 10 external пропущены с той же причиной. В репозитории таких кейсов 38.
Решение перенесено к остальным ранним пропускам, до Step 0. Обе ветки
сохранены: недоступная выгрузка (её нет на маке) по-прежнему даёт СКИП, а не
падение, доступная — passed с noPlatformReason. Ставшие недостижимыми блоки в
Step 0 и Step 7 удалены, caseProvidedConfig сузился до fixture:.
Фикстурные кейсы не затронуты — прогон meta-validate до и после совпадает
(17 прошли, 0 упало, 13 пропущено).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Правило было уже реальности: Конфигуратор не даёт включить характеристику в
составной тип вместе ни с каким другим типом — в том числе с другой
характеристикой. Предупреждение теперь ловит и DefinedType.X, и
Characteristic.X.
Корпус подтверждает даже чище, чем для определяемого типа: 1741 характеристика
единственным типом и 0 в составных (у определяемого было 6500 против 1).
Уровень прежний — предупреждение: загрузчик такое принимает, проверено
загрузкой в базу.
Голых метатипов это не касается: DocumentRef + CatalogRef — обычный составной
тип, 525 вхождений в поставляемых ERP и БП.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Условие было шире реальности: предупреждение пропускало и Characteristic.X.
В дереве выбора типа значения ПВХ характеристики нет вовсе — тип значения
характеристики не может быть значением характеристики. Единственное множество,
которое Конфигуратор там предлагает, — ОпределяемыйТип.
Уровень прежний: платформа такую конфигурацию грузит (проверено загрузкой в
базу), поэтому предупреждение, а не отказ. В корпусе erp+acc ни один из 24 ПВХ
множеств в типе значения вообще не использует.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Три находки, все воспроизведены:
1. Счётчик членов составного типа не видел форму с локальной xmlns:
тип из чужого пространства имён пишется как <v8:Type xmlns:mxl="…">, а
регулярка требовала '>' сразу за именем тега. В итоге на одном и том же
файле meta-compile молчал, а meta-validate предупреждал — навыки
расходились в оценке одного содержимого. Радиус: оба порта.
2. Report-OK проверки 23 печатался безусловно — то есть сразу после
собственного ERROR, и раздувал счётчик проверок в итоговой строке.
Соседние проверки (21, 22) так не делают.
3. ToString() копировал весь буфер вывода на каждый реквизит: O(n^2) на
крупном объекте. В файле уже есть ranged-перегрузка ровно для этого
(Emit-TypeContent). py-порт был изначально корректен — он режет список.
Добавлен кейс на тип с локальной xmlns: без него находка 1 вернулась бы
незамеченной, потому что обычный составной тип её не показывает.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Запрет неочевидный, а отказ платформы приходит поздно и одной строкой из
Конфигуратора. Три уровня строгости, все замерены загрузкой в базу на
8.3.24.1691, а не выведены из документации:
- ОШИБКА — множество в составе определяемого типа. Платформа отвергает файл
целиком («ОпределяемыйТип.<Имя> - Недопустимый тип»); проверено на
ОпределяемыйТип, Характеристика, ЛюбаяСсылка и голых ссылках, контрольный
вариант с обычным CatalogRef.<Имя> принят. Отсюда и ноль таких узлов в
корпусе — это запрет, а не совпадение.
- ПРЕДУПРЕЖДЕНИЕ — голый метатип в типе значения ПВХ. Загрузка проходит, но
Конфигуратор такой тип не предлагает: в дереве выбора СправочникСсылка и
ДокументСсылка — папки без флажка, а ЛюбаяСсылки там нет вовсе. Плюс AnyRef
раундтрипом возвращается как AnyIBRef, то есть молча меняется.
- ПРЕДУПРЕЖДЕНИЕ — определяемый тип одним из составного. Конфигуратор даёт
выбрать его только единственным, но отказывать нельзя: в корпусе erp+acc
6500 единственных против 1 составного, и этот один лежит в типовой ERP
(Документ.НачислениеИСписаниеБонусныхБаллов.Баллы — два определяемых типа
подряд). Навык не вправе отказаться собрать то, что поставляет 1С.
В meta-compile состав множеств берётся опросом самого эмиттера, а не второй
копией его регулярок: список видов живёт в Emit-TypeContent, и копия
разъехалась бы с ним молча.
Развёртка по 3428 объектам ERP: ровно 2 срабатывания, оба на том самом
реальном составном реквизите. Ложных нет.
Предупреждения пишутся прямо в stderr, а не через Write-Warning: в PS 5.1 тот
уходит не в тот поток (конвенция из cf-init), и до проверки они не доезжали.
Оба кейса-предупреждения проверены платформой — она эти конфигурации грузит,
что и подтверждает выбор «предупреждение, а не отказ».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Правка нарушала два наших правила сразу. Фраза «русские имена принимаются
наравне с английскими» описывала прощающий ввод — его документировать нельзя,
иначе модель получает развилку вместо одной каноничной формы. Замечание «в
выгрузке ERP так задана треть подписок» — про наше исследование, а не про
использование навыка: следов фикса в инструкции не держим.
Формы источника остаются описанными в docs/meta-dsl-spec.md, а круг
«вывод meta-info → вход meta-compile» держит check-typeset-coverage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Семь находок ревью, все подтверждены воспроизведением:
1. Голый менеджер в источниках печатался по-английски: карта объектных видов
применялась только к форме с точкой, а meta-compile пишет «cfg:DocumentManager»
обычным v8:Type. Восемнадцать добавленных *Manager-записей были недостижимы.
Суффикс «(все)» ему не ставится — это сам тип менеджера, а не класс объектов.
2. Новые регулярки прощающего ввода расходились между портами: -replace в
PowerShell регистронезависим, re.sub — нет. «ДокументОбъект (Все)» проходил в
ps1 и падал в py. Добавлен re.IGNORECASE — это задокументированная ловушка
портирования, и она же снова сработала.
3. В шесть py-портов попал BOM (перекодировка при бампе версии), и перед
«#!/usr/bin/env python3» он ломает shebang на POSIX. Снят там, где его не было
в HEAD; в mxl-compile.py он был изначально и оставлен.
4. Правило со стрелкой из Resolve-TypeStr убрано. form-compile режет тип по [|+]
ДО резолвера, поэтому строка глоссария «A -> B | C» молча превращалась в
составной тип «A | C» вместо одного A. Половина строки, принятая за тип, —
хуже громкого отказа. Остались суффикс «(все)» и счётчик «— типов: N».
5. py-порт meta-info не переиспользовал общий климб до корня конфигурации:
правка тогда молча не применилась, и порты разошлись по структуре.
6. Found выставлялся до разбора состава определяемого типа: падение внутри
давало «состав пуст» — ложь вместо «файл типа не разобран».
7. Порог сворачивания источников считался по сумме множеств и явных типов, а
сворачивались только явные: пять множеств плюс один тип прятали этот тип, а
шесть типов без множеств давали «и ещё» после пустоты. Порог теперь по числу
явных типов, «и ещё» убрано как ложное.
Плюс формы источника подписки описаны в reference самого навыка: раньше они
были только в docs/meta-dsl-spec.md, а навык обязан быть самодостаточным.
Добавлен кейс на голого менеджера — без него находка 1 вернулась бы незамеченной.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
verify-snapshots отверг кейс: «ОпределяемыйТип.Любой - Недопустимый тип».
Спросил платформу — v8:TypeSet внутри определяемого типа запрещён вообще, и не
только AnyRef: отвергнуты также голый CatalogRef, Characteristic.X и вложенный
DefinedType.X, при том что контрольный вариант с обычным CatalogRef.X принят.
Замерено на 8.3.24.1691 загрузкой в базу. Отсюда и ноль таких узлов в корпусе —
это не совпадение, а запрет.
Комментарий в обоих портах утверждал обратное («платформа допускает») — правда
записана, факт добавлен в docs/meta-dsl-spec.md.
Сам разбор v8:TypeSet в выводе определяемого типа оставлен: meta-info читалка,
и рукотворный файл с таким узлом она обязана показать честно, а не потерять его
молча вместе с правдивым счётчиком. Кейс переведён на рукотворную фикстуру с
объявленной причиной пропуска платформенной проверки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Покрытия не было вообще: ни одного кейса ни на подписку на событие, ни на
определяемый тип. Поэтому дефект и жил — тесты были зелёными всё время.
Добавлено девять кейсов:
- источники-множества в full/overview/brief (класс «все», определяемый тип,
явный тип) — основа взята из PR #96, эталоны под наш формат;
- определяемого типа нет в выгрузке: сказано прямо, а не молчанием;
- односоставный псевдоним в типе реквизита — главный по частоте случай в
реальных выгрузках (7575 вхождений из 8841 в ERP);
- определяемый тип выше порога: вместо состава счётчик и команда раскрытия —
этот кейс держит решение «не раскрывать», без него регрессия вернула бы
шестисотстрочный вывод;
- голый метатип в типе реквизита: суффикс «(все)» против конкретного типа;
- определяемый тип, содержащий множество: счётчик «Типы (N)» его не теряет;
- явных источников больше порога: overview сворачивает их в счётчик.
Фикстуры строятся навыками через preRun, чтобы эталоны не плавали по uuid.
Co-Authored-By: Шпаков Антон Александрович <shpakov.anton.job@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Раздел знал только ссылочные метатипы, хотя meta-compile объектные эмитит
правильно, а источники подписок на событие построены именно на них: в
erp_8.3.24 так задана треть подписок. Дописаны объектные виды, наборы записей
и ConstantValueManager, отмечено, что прочие менеджеры множеством не являются,
и зафиксирован суффикс «(все)» в выводе meta-info.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Связь «прочитал вывод → подал на вход» держалась на честном слове и рвалась
молча в обе стороны: meta-info печатал метки, которых нет в словаре
meta-compile (33 из 41), а в карте meta-info не было видов, которые
meta-compile эмитит, — и они печатались по-английски посреди русского вывода.
Снэпшоты этого не видят: кейсы читают и компилируют по отдельности, круг не
замыкая.
Инвариант строгий в одну сторону: каждая метка читалки обязана быть ключом в
typeSynonyms у meta-compile и вести ровно в тот же канон. Обратное неверно
намеренно — компилятор знает формы, которых читалка не печатает. Заодно гард
сверяет objectTypeMap и refTypeMap между портами навыка: переводы — часть
вывода, и расхождение портов дало бы разный текст на один и тот же файл.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Вывод читающего навыка модель несёт обратно в компилятор, а тот его не
понимал. Из 41 метки, которую печатает meta-info, в словаре typeSynonyms не
было 33: «ДокументОбъект.Заказ» из источников подписки meta-compile отвергал с
«Неизвестный тип», хотя сам же печатал эту форму через meta-info. Не
принимались и «ПВХСсылка.ВидыСубконто», и «Характеристика.Виды» — при том что
Characteristic.Xxx описан в спеке как штатная форма DSL.
Добавлены русские имена объектных типов, менеджеров и наборов записей.
Аббревиатуры (ПВХ/ПВР, РС/РН/РБ/РР) приняты наравне с полными именами: вывод
навыка сокращает долгие виды, чтобы в списке на сорок реквизитов не терялось
имя объекта, и раз он их печатает — обязан принимать.
В Resolve-TypeStr снимаются хвосты, которые дописывает глоссарий meta-info:
суффикс «(все)», счётчик «— типов: N» и раскрытие после стрелки. Срезаются
только эти известные формы — круглые скобки заняты параметризованными типами
вроде Число(15,2), слепой срез сломал бы их.
Функция под гардом check-inline-drift, поэтому правка синхронна во всех семи
навыках и обоих портах.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Источники подписки на событие читались только из v8:Type. Если источник задан
множеством — определяемым типом или целым классом «все документы» — строки
«Источники» не было вовсе, и подписка выглядела так, будто ни на что не
срабатывает. В erp_8.3.24 так задана треть подписок: 151 из 492 вообще без
v8:Type, в acc_8.3.24 — 126 из 443.
Тот же дефект в типах реквизитов, и там он чаще. Вхождений
v8:TypeSet>cfg:DefinedType в Catalogs/Documents/InformationRegisters/
AccumulationRegisters ERP — 8841, из них 7575 (86%) указывают на определяемый
тип ровно с одним членом. АвансовыйОтчет печатал «Сумма
ОпределяемыйТип.ДенежнаяСуммаНеотрицательная», и было не видно, что это
Число(15,2): имя выглядит как ссылочный тип. Голый метатип-категория
(«ДокументСсылка» без точки = любой документ) отличался от конкретного
«ДокументСсылка.Заказ» только наличием точки посреди длинного имени.
Правило одно на все места: имя множества стоит в строке типа, а раскрытие —
ровно одной записью на уникальное множество, глоссарием в конце вывода.
Раскрывать в строке нельзя дважды: псевдоним повторяется в объекте десятками
раз (в АвансовомОтчете — 12), а в корпусе есть определяемые типы на 596 типов —
вывод упёрся бы в постраничник и съел хвост объекта. Выше порога
composedTypeThreshold вместо состава идут счётчик и готовая команда раскрытия.
Голый метатип получает суффикс «(все)»: разница становится словом, а не
пунктуацией. Характеристика ПВХ помечена как набор, определяемый данными, —
из выгрузки её состав не берётся в принципе.
Попутно того же класса:
- объектные типы (DocumentObject.X, *RecordSet.X, *Manager.X) печатались
по-английски в выводе определяемого типа и по-русски в источниках подписки —
одно понятие двумя видами; в карту добавлены недостающие виды, включая
ConstantValueManager и ChartOfCalculationTypesObject;
- собственный вывод определяемого типа читал только v8:Type и молча терял
v8:TypeSet — счётчик «Типы (N)» врал;
- корень конфигурации ищется климбом до Configuration.xml, общим с проверкой
поддержки, а не фиксированным «на два уровня выше»;
- состав определяемого типа кэшируется по имени;
- нечитаемый файл типа назван нечитаемым, а не отсутствующим.
Проверено корпусом: 5129 прогонов по erp_8.3.24 и acc_8.3.24 в трёх режимах,
каждое расхождение с эталоном до правки классифицировано, необъяснённых нет.
Паритет портов — 5129 из 5129, 103912 строк совпали.
Проблему и замеры по подпискам сообщил автор PR #96.
Co-Authored-By: Шпаков Антон Александрович <shpakov.anton.job@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PowerShell ищет функцию в момент вызова и видит только уже выполнившиеся
объявления. В трёх навыках блок Find-V8Project/Test-SamePath/Find-ProjectDatabase
оказался ниже строки, где отрабатывает Find-ProjectV8Path: CommandNotFoundException
уходил в stderr, результат становился $null, и выбор платформы по записи базы молча
откатывался на корневой v8path. Порты .py не задеты — Python связывает имя при
вызове, так что расхождение PS↔PY было бы тихим.
Гард check-ps-define-before-call.mjs проверяет порядок объявлений во всех .ps1,
где отрабатывает эта цепочка (15 навыков), и на прежнем состоянии даёт ровно те
три диагностики. Кейс db-repo/v8path-from-database ловит тот же дефект поведением.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>