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
Удалённый объект уходит из базы, только если в списке его владелец и в
составе владельца объекта уже нет; удалённый файл-часть (модуль, 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
Ветка параметра читала заголовок обычным 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>
Правило было уже реальности: Конфигуратор не даёт включить характеристику в
составной тип вместе ни с каким другим типом — в том числе с другой
характеристикой. Предупреждение теперь ловит и 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>
Вывод читающего навыка модель несёт обратно в компилятор, а тот его не
понимал. Из 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>
В одном проекте базы могут жить на разных версиях платформы, а версию формата
XML-выгрузки задаёт та платформа, которая выгружает: без выбора платформы по базе
срабатывает автопоиск и берёт самую старшую установленную — то есть самый новый
формат, который коллега на младшей платформе уже не загрузит.
Теперь порядок такой: явный -V8Path -> v8path записи базы -> корневой v8path ->
автопоиск. Запись базы ищет сам скрипт тем же матчем, которым добывал реквизиты
хранилища (Find-ProjectDatabase): файловую по path, серверную по server + ref.
- в 9 навыков, где матча не было, скопирован блок из авторитета db-repo;
- списки потребителей трёх семей в check-inline-drift расширены; заведена семья
platform: resolve_v8path — обёртка меняла сигнатуру, а под гардом не была;
- раннер подставляет {workDir}/{fakePlatform} и в содержимое preRun.writeFile,
иначе кейс про выбор платформы нельзя написать кроссплатформенно;
- в SKILL.md платформа теперь берётся ПОСЛЕ разрешения базы, строка про
автоопределение через Program Files убрана как деталь реализации.
Живой прогон: две базы в одном проекте при корневом v8path = 8.5 дали формат
2.17 (8.3.24) и 2.20 (8.3.27), оба порта.
Closes#93
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Две находки ревью.
Приняв хвостовой комментарий у маркера, разбор запоминал только содержимое
блока, а -Actualize писал на его место голое ключевое слово: авторская пометка
вроде «#Вставка // проверка прав, ТЗ-142» молча исчезала при первой же
актуализации. Теперь строки открывающего и закрывающего маркера едут вместе с
блоком и возвращаются как были — с комментарием, отступом и своим написанием;
ключевое слово остаётся запасным вариантом. Кейс actualize-marker-tail-comment
эту потерю ловит: без правки падает.
Счётчик контролируемых методов в cfe-validate был единственным новым
сопоставлением без учёта регистра, хотя язык регистронезависим: на
&changeAndValidate он показывал ноль и расходился с cfe-patch-method -Check,
на который сам же и ссылается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Пара And/И в таблице была, а Or/Или — нет: при снятии пар с таблицы строк
платформы односимвольные слова отсеял фильтр длины, и пропуск не бросался в
глаза. Сегодня кода, который эмитирует Или, нет (условия собираются только из
НЕ и И), так что дефекта это не давало, но таблица — единственное место
правды о языке, и дыра в ней ждала своего часа.
Заодно подписано, почему отрицание эмитируется как "НЕ": платформа пишет
"Не", язык регистронезависим, а смена регистра сдвинула бы все эталоны.
Val, Insert/EndInsert и Delete/EndDelete перепроверены на платформе, а не по
дампу бинарника: их английские написания в таблице строк не видны из-за
склейки одинаковых литералов. Оракул — проверка применимости расширения:
Val Item и Знач Item платформа считает одним списком параметров, блоки
#Delete/#EndDelete принимает, а подсунутый #Deletee отвергает
(«Ожидается оператор препроцессора»).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа принимает встроенный язык в двух написаниях, а навыки читали только
русское: в расширении на английском перехватчики и блоки правок не находились,
и отчёт выглядел чистым. -Check молчал, cfe-diff не показывал вставки,
счётчик контролируемых методов в cfe-validate давал ноль.
Пары ключевых слов сверены по таблицам строк платформы (backbas.dll, bsl.dll),
а не по памяти. Разбор стал двуязычным везде, где навык читает модуль:
аннотации, объявления и Конец*, Знач, директивы контекста, препроцессор в
цепочке обрамления, поиск региона при переиспользовании.
Маркеры правок распознаются по началу строки, а не сравнением целой строки, —
заодно перестал теряться хвостовой комментарий (#Вставка // старая логика).
Эмиссия подчиняется одному правилу: отдаём тем языком, который прочитали.
Генерация берёт язык метода-источника, -Actualize — язык переписываемого
перехватчика, чтобы актуализация не превращала чужие Procedure и #Insert в
русские. Комментарии и вывод в консоль остаются русскими.
Таблица ключевых слов и разбор аннотаций скопированы в три навыка и заведены
семьями в реестре анти-дрейфа: разъехавшаяся пара слов дала бы ровно тот же
тихий ложно-чистый отчёт.
Проверено платформой: расширение с &ChangeAndValidate принимается
(/CheckCanApplyConfigurationExtensions), после дрейфа оригинала отвергается,
после -Actualize принимается снова.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Замена ExternalReport. -> Report. шла по всему тексту XML и попадала внутрь
имени объекта, если оно оканчивается так же (ПриёмкаExternalReport). Имя берётся
из <Name> до замены и остаётся авторским везде — в имени файла, GeneratedType и
составе конфигурации, — а ссылка в скопированном XML расходилась с ним, и
заглушка не грузилась: «Неизвестный объект метаданных - Report.ПриёмкаReport…».
Заменяется только префикс вида объекта в начале квалифицированного имени.
Регрессии строят фикстуру навыками (erf-init --WithSKD, epf-init, form-add) —
так в кейс попадает и настоящая ссылка вида cfg:ExternalReportObject.<Имя>.
Соседние кейсы каталога переведены на {fakePlatform} и прямые слэши.
Closes#90
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В py-порте QUERY_BASE_DIR инициализировался None и заполнялся только в
ветке с -JsonPath: без него os.path.join(None, ...) упал бы. Путь
недостижим — запросы приходят только из JSON, — но PS-порт в этой ветке
берёт текущий каталог, и расхождение стоит закрыть до того, как оно
станет достижимым.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
1. Резолв uuid вложенного объекта переписан со сквозной регулярки на
спуск по дереву. Поиск по всему файлу брал первый узел с подходящим
именем, а реквизит шапки и реквизит табличной части сплошь и рядом
называются одинаково — ключ сортировки был чужим и вдобавок
дублировался, из-за чего порты могли разложить узлы по-разному.
Дочерние подсистемы лежат отдельными файлами и не резолвились вовсе:
431 узел в ролях ACC. Теперь на реальной роли резолвится 90% узлов,
остальное — стандартные реквизиты, у которых uuid в выгрузке нет.
Для них предупреждение больше не печатается: это норма, а не потеря.
2. Фильтр умолчаний уносил право вместе с его ограничением RLS: условие
пропадало молча. Право с ограничением отличается от умолчания самим
ограничением и остаётся.
3. modify-property не обновлял кэш умолчаний, и следующие операции того
же вызова фильтровали по старому флагу — запись попадала в файл ровно
вопреки тому, о чём навык сам предупреждает.
4. Edit-RoleMetadata перечитывал Roles/Имя.xml на каждой операции, и в
пакете первая правка молча терялась: set-synonym + set-comment
сохранял только комментарий. В py-порте такого не было — расхождение
портов закрыто.
5. Атрибуция сообщения в PS шла подстрокой по всему списку отброшенного,
из-за чего причина приписывалась чужому объекту. Теперь префиксное
сравнение, как в py.
Три новых кейса, семьи get_rights_object_uuid и is_standard_kind
заведены в check-inline-drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
Два дефекта, найденных вычиткой перед ревью.
deny-rights создавал узел объекта и, если фильтр умолчаний отбрасывал
все запреты, оставлял его пустым — в файл он попадал, когда сохранение
инициировала соседняя операция. Платформа пустых узлов не производит
(0 на 223k узлов корпуса). Теперь созданный впустую узел убирается.
Правило удаления узла было «не осталось разрешающих прав» — наследие
решения «false это шум». Замеры показали обратное: узел с одними
запретами осмыслен, так закрывают реквизит. Узел удаляется, только если
в нём не осталось прав вообще.
Там же закрыт разворот массива: `return @(...)` из функции отдаёт
единственный элемент скаляром, у которого .Count равен $null, поэтому
узел с ОДНИМ правом считался пустым и удалялся целиком. Поймал гард
минимального дифа: снятие одного права давало -11 строк вместо -4.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
Таблица «что хранится при каких умолчаниях роли» ушла из инструкции:
модель попадает в этот случай ровно в момент, когда навык уже печатает
«запрет совпадает с умолчанием роли и платформой не хранится». Вместо
описания механики сообщение дополнено подсказкой, где запрет имеет
смысл, — теперь вывод самодостаточен, а инструкция короче.
Там же сжаты формулировки, которые дублировали текст ошибок: про отказ
до записи, про право сервиса на корне, про ограничение RLS без права.
Назначение deny-rights в таблице операций названо прямо: закрыть
реквизит или ТЧ, которые иначе наследуют права объекта.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
Убран раздел про русские синонимы: прощающий ввод в инструкции даёт
модели развилку там, где есть одна каноничная форма — английская.
Убран раздел про замыкание набора при выдаче: навык перечисляет
дописанное в выводе, а в инструкции это лишняя механика. Оставлено
только то, что меняет решение ДО вызова: снятие и запрет уносят больше
перечисленного.
В примере объектной формы запрет переехал с объекта верхнего уровня на
реквизит: на верхнем уровне такая запись при умолчаниях роли не
хранится, и пример учил бесполезному.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
Замер на платформе (debug/role-edit/FINDINGS.md): роль хранит только то,
что отличается от её умолчаний. При setForNewObjects=false на верхнем
уровне живут разрешения, а любой запрет выбрасывается (проверены Update,
Edit, Delete; узел из одних запретов удаляется целиком); при true —
наоборот. У реквизитных вложенных ту же роль играет
setForAttributesByDefault. Конфликт решается в пользу разрешения.
Отсюда три правки, общие для обоих навыков:
1. Прямое замыкание идёт только от РАЗРЕШЁННЫХ прав. Раньше запрет тянул
зависимости как разрешения: "Catalog.X: {Edit: false}" выдавал
Read, Update и View — навык раздавал права на основании запрета.
2. Появилось обратное замыкание: запрет уносит права, которым
запрещённое нужно. Сверено с платформой — при setForNewObjects=true
она к Update=false дописывает те же десять запретов.
3. Записи, совпавшие с умолчанием роли, не пишутся: платформа их всё
равно выбросит, а файл разошёлся бы с базой. Отброшенное
перечисляется в stderr, сообщение операции объясняет причину.
Правило применяется только там, где замерено: внешние источники данных
под него не попадают. Обе функции заведены семьями в check-inline-drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq