No description
  • 1C Enterprise 75.8%
  • Python 21.9%
  • PowerShell 2.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-25 22:14:29 +03:00
calibrate_extensions/Калибровка_Расш Повтор СхемаЗапроса: модуль менеджера через хук формы и СКД отчета (этап 5) 2026-10-09 17:19:57 +03:00
calibrate_harvest WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
calibrate_harvest_vendor WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
calibrate_parity Обезличивание: без привязки к ICL и Техно, удален мертвый код harvest 2026-10-09 06:41:14 +03:00
src WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
.gitattributes Initial commit: CompileQueryTexts - смоук-компиляция текстов запросов 2026-09-28 13:15:29 +03:00
.gitignore WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
ALGORITHM.md WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
calibrate_harvest.py WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
calibrate_parity.py WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
calibrate_sites.json Ложные fail на Техно 2.5.27.75: ВТ вне текста, хвост ИНДЕКСИРОВАТЬ, дописывание в Если/Иначе, &Имя внутри образца 2026-10-09 21:58:41 +03:00
compile_queries.params.example.json Обезличивание: без привязки к ICL и Техно, удален мертвый код harvest 2026-10-09 06:41:14 +03:00
compile_queries.ps1 Ложные fail на Техно 2.5.27.75: ВТ вне текста, хвост ИНДЕКСИРОВАТЬ, дописывание в Если/Иначе, &Имя внутри образца 2026-10-09 21:58:41 +03:00
CompileQueryTexts.epf WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
harvest_query_sites.py WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
README.md WIP: фиксация незакоммиченных изменений перед переносом на git.balk.one 2026-10-25 22:14:29 +03:00
SMOKE_SQL_CHECKER_алгоритм.pdf что то 2026-10-03 20:22:47 +03:00
SMOKE_SQL_CHECKER_доклад.pptx что то 2026-10-03 20:22:47 +03:00
SMOKE_SQL_CHECKER_как_работает.pdf что то 2026-10-03 20:22:47 +03:00
SMOKE_SQL_CHECKER_как_собирается_текст.pdf что то 2026-10-03 20:22:47 +03:00
v8project.yaml Initial commit: CompileQueryTexts - смоук-компиляция текстов запросов 2026-09-28 13:15:29 +03:00
Встреча в Телемосте 30.09.26 18-03-32 — запись.txt что то 2026-10-03 20:22:47 +03:00

SMOKE_SQL_CHECKER

Пакетная компиляция доработанных текстов запросов живым объектом Запрос (.Текст + .НайтиПараметры()). Не Vanessa, не конструктор, не /CheckConfig. Не правит конфигурацию.

Канон — этот каталог. Change audit-customized-query-texts не архивировать: там restore по .mdo.

ИБ компиляции должна быть той, куда загружено проверяемое дерево исходников. Иначе отчёт про другую конфигурацию.

1. Основной путь: форма EPF

Собрать обработку (после правки исходников):

powershell.exe -NoProfile -File "<каталог скрипта сборки>\epf-build.ps1" `
  -SourceFile "<каталог проекта>\src\CompileQueryTexts.xml" `
  -OutputFile "<каталог проекта>\CompileQueryTexts.epf"

Открыть CompileQueryTexts.epf в предприятии (ИБ с проверяемой конфигурацией).

Версия обработки - функция ВерсияОбработки() модуля объекта; заголовок формы строится из нее, синоним в src\CompileQueryTexts.xml держать в том же значении. Обработку можно зарегистрировать в справочнике БСП "Дополнительные отчеты и обработки": СведенияОВнешнейОбработке() возвращает вид "Дополнительная обработка" с командой открытия формы и БезопасныйРежим = Ложь (обработка читает каталоги и пишет файлы, регистрирует ее администратор). Модули БСП вызываются через Вычислить только при их наличии, поэтому в базе без БСП обработка компилируется и работает как раньше.

  1. Каталог исходников - готовое дерево исходников основной конфигурации, только читается. Формат определяется сам по содержимому каталога: Configuration.xml - выгрузка Конфигуратора, Configuration\Configuration.mdo (в каталоге, его src или edt\src) - EDT; ни того ни другого - отказ. Формат определяется отдельно для каждого каталога (исходники, вендор, каждое расширение), смешивать форматы можно. Переключателя формата и выгрузки текущей ИБ Конфигуратором на форме нет (убраны 04.10.2026): дерево исходников готовится заранее.
  2. Каталог типовой конфигурации (вендор) - обязателен: исходники типовой конфигурации того же релиза, EDT src или выгрузка Конфигуратора (формат определяется по Configuration.xml / Configuration\Configuration.mdo). Полные версии нашего дерева и вендора должны совпадать, иначе прогон отказывает до сбора; основной каталог не должен быть расширением. Выбор папки через БСП ФайловаяСистемаКлиент.ВыбратьКаталог (при отсутствии модуля - прежний диалог).
  3. Флаг Используются расширения и Каталог расширений: каждое расширение лежит в своей подпапке, имя подпапки совпадает с именем расширения в базе (проверяется по файлу описания подпапки, иначе отказ). Каждое расширение - отдельный корень сбора с меткой = имя подпапки, сравнивается с тем же вендором. Методы &ИзменениеИКонтроль собираются в том виде, в каком их исполняет платформа: блок #Удаление ... #КонецУдаления выкидывается целиком, строки #Вставка / #КонецВставки выкидываются, тело вставки остается (правило V7, номера строк не сдвигаются). Проверка активности расширений в сеансе - этап 8 плана, пока не сделана.
  4. Проверить - harvest по сравнению с вендором тиками: раскрытие одной верхней папки метаданных (Documents, Catalogs, CommonModules, …) — отдельный тик (НайтиФайлы по маскам); затем пакеты по 300 файлов на тик. Форма живая: два индикатора — По объекту (доля текущего шага: файлов верхней папки, файлов дерева при поиске T7b, сайтов при компиляции) и Общий (по этапам всей проверки, только растет: сбор 0-70% - корни конфигурации и расширений равными долями, внутри корня по верхним папкам; поиск образцов T7b 70-90%; компиляция 90-100%, ход компиляции - в окне ожидания БСП; 100% - только после получения итога компиляции); текст Сбор: CommonModules: объект 120/4492; папки 9/21. Стоп между пакетами прерывает сбор без компиляции и без завершения сеанса (шкалы обнуляются). Файл, текст которого совпадает с файлом вендора, не разбирается. После сбора, если у сайтов есть кандидаты T7b, - фаза поиска образцов замен по дереву (пакеты по 600 файлов на тик). Компиляция - фон БСП ДлительныеОперации после последнего тика. Двоичные данные EPF читаются на клиенте и передаются на сервер через временное хранилище (кластер не открывает путь D:\...epf). Для фона внешней обработки: новая БСП — НовоеОписаниеВнешнегоОтчетаОбработки; БСП без этого метода (ЕРП УХ) — в ВнешнийОтчетОбработка передаются сами ДвоичныеДанные. Без модулей БСП длительных операций компиляция с формы отказывает до начала сбора. Отказы и ошибки прогона — через БСП ОбщегоНазначенияКлиент.СообщитьПользователю (fallback на СообщениеПользователю, если модуля нет).

Шапка параметров - три поля каталогов одной ширины (исходники, вендор, расширения) и под ними флаг "Используются расширения". Таблица результата занимает остаток окна. Заголовки колонок: Принадлежность, Путь, Объект, Процедура, Основание, Статус, Ошибка. Принадлежность - корень сбора сайта (root): Конфигурация или Расширение <Имя> (имя подпапки каталога расширений); сайт без root (пакетный /C по JSON Python-зеркала) - Конфигурация. Колонка заменила префикс [метка] в Пути (до 04.10.2026). Название не Источник: это поле в СКД-отчете уже занято (source сайта - модуль, форма, макет). Основание - поле kind сайта: differs (у вендора есть, отличается), absent-in-vendor (у вендора нет процедуры), module-absent-in-vendor (файла нет у вендора: собственный модуль, модуль, удаленный вендором, или сторонний продукт); то же поле выводится в СКД-отчете. В колонке статуса русские подписи: Успех / Ошибка / Не разобран / Нет контекста. Строка итога: успех=… ошибка=… не разобран=… нет контекста=… всего=…. Клик по строке показывает полный текст ошибки в поле под таблицей. Галка Только ошибки скрывает строки «Успех» только в таблице формы (оставляет ошибку, не разобран, нет контекста и строки с признаком холостой замены - статус «Успех, холостая замена»); по умолчанию включена и не сохраняется в настройках.

Кнопка Отчет открывает табличный документ СКД той же EPF по последнему итогу (не ERF, не ОбщаяФорма.ФормаОтчета, без сохранения пользовательских вариантов БСП). В макете предопределённые варианты: По объекту (Основной), По статусу, Список; команда открывает Основной (группировка объект / статус, текст запроса, краткая ошибка платформы). В отчёт входят неуспешные строки (fail / unparsed / needs-context) и успешные с признаком холостой замены (поле Требует внимания). Колонки отчета: Объект, Статус, Принадлежность, Путь, Процедура, Ошибка, Признаки схемы, Вклад схемы, Холостые замены, Текст запроса, Источник, Основание, Строка, Разбор (набор и порядок - во всех трех вариантах). Признаки схемы - многострочно, для сайтов schema-replay: класс отказа failKind, schemaNoEffect, нарушения schemaInvariant, флаги словами (вендор сам правит текст, базовый текст из кода вендора, условный выход перед блоком, вывод внутри Если, хвост СтрЗаменить отрезан, расширение не применено), форма replayForm, источник базового текста baseSource. Вклад схемы - записи schemaContribution по строке. Холостые замены - только T7, T7b и T9. Тексты колонок и поля под таблицей формирует одна функция формы (ТекстПризнаковСхемы, ТекстВкладаСхемы). Строки повтора схемы без признаков внимания (статус ok, только флаги или вклад) попадают в набор данных с Требует внимания = Ложь: отбор по умолчанию их скрывает; при снятой галке Только ошибки команда выключает отбор по Требует внимания, и они попадают в документ. Без прогона — отказ, пустой документ не открывается. JSON /C: коды ok / fail / unparsed / needs-context и counts.needsContext без изменений; у элемента есть поле query (собранный текст, для unparsed пусто) и краткий error.

Harvest сайтов: один сайт = один строковый литерал, похожий на запрос (или один статически склеенный результат СтрЗаменить). Склейка | внутри литерала не трогается. Соседние литералы одной процедуры не склеиваются через ; (иначе ложный «ВТ уже существует» / «ожидается ВЫБРАТЬ»). Пакет из нескольких запросов внутри одного литерала остаётся одним сайтом. Строки BSL с ведущим // (после пробелов) не входят в извлечение литералов. Шаблоны НСтр (ru = / en =) не считаются запросом. Маркеры ВЫБРАТЬ / целое слово SELECT дают сайт только вместе с SQL-контекстом (целое ИЗ/FROM или ПОМЕСТИТЬ/СОЕДИНЕНИЕ/JOIN); сильные маркеры ПОМЕСТИТЬ / УНИЧТОЖИТЬ (целым словом) / DROP TABLE — сами по себе. Целое слово обязательно: имя временной таблицы, собираемое строкой ("УничтожитьТоварыСОшибкамиВКоличестве" + Номер), содержит ключевое слово подстрокой и запросом не является (кейс НоменклатураСервер.ПроверитьЗаполнениеСерий).

Отбор доработанного кода - сравнение с вендором (02.10.2026, оба зеркала; маркеры доработок не используются). Файлы нашего дерева и дерева вендора сопоставляются по ключу без сегментов Ext (EDT и Конфигуратор взаимозаменяемы). Файл, совпадающий с вендорским, не разбирается. Модуль режется на процедуры (заголовок - строка Процедура / Функция, допустимы Асинх и табуляция, есть (; повтор имени - Имя#2) и текст вне процедур; единица отбирается, если ее нет у вендора или она отличается без учета пробельных символов (комментарии учитываются: измененный комментарий - изменение). queryText формы и query СКД отбираются, если такого текста нет среди текстов того же файла вендора. Основание - в kind (см. выше). Зеркало: форма EPF (СобратьСайтыBSLПоСравнению, ЕдиницыСравненияМодуля, СравнимыйТекст, КлючФайлаИсходников, ПутьФайлаВендора), harvest_query_sites.py (select_sites, bsl_unit_sites, proc_units, compare_normal, unit_key). Правила V1-V6 - ALGORITHM.md, раздел 3а. На рабочей конфигурации сравнение заменило 169 ложных отборов по маркерам (процедуры, равные вендорским) и нашло 21 изменение кода без маркера.

Фрагменты (не компилируются): литералы с @query-part в теле (комментарии учитываются до среза //); куски с ПОМЕСТИТЬ без ведущего ВЫБРАТЬ/SELECT/УНИЧТОЖИТЬ/DROP; старт с ИСТИНА В / НЕ ИСТИНА В; склеечные куски без корня — старт с ОБЪЕДИНИТЬ / ОБЪЕДИНИТЬ ВСЕ, ЛЕВОЕ|ПРАВОЕ|ВНУТРЕННЕЕ|ПОЛНОЕ СОЕДИНЕНИЕ (и LEFT|RIGHT|INNER|FULL JOIN), ведущая ;, предикат ( / И / ИЛИ / ГДЕ /WHERE , хвост с УНИЧТОЖИТЬ/DROP TABLE не в начале — сайт не создаётся. СКД/построитель: литералы с {ВЫБРАТЬ или {ГДЕ — не сайт. Обрыв конкатенации: корень, который после снятия | заканчивается на ИЗ ( / FROM ( (левая половина "+Выражение+") — не сайт. Тот же обрыв перед именем таблицы: корень, заканчивающийся на голый вид таблицы с точкой без имени объекта (Справочник. / Документ. / РегистрНакопления. / РегистрСведений. / РегистрБухгалтерии. / РегистрРасчета. / ПланСчетов. / ПланВидовХарактеристик. / ПланВидовРасчета. / Перечисление. / БизнесПроцесс. / Задача. / ПланОбмена. / Константа. / ДокументЖурнал. / ВнешнийИсточникДанных. / Последовательность.) — левая половина "...Вид." + ИмяТаблицы + "...", имя таблицы подставляется в рантайме — не сайт, попадает в unparsed («Текст запроса собирается в рантайме»), если это единственная конструкция в процедуре. Одиночный корневой УНИЧТОЖИТЬ ВТ / ВЫБРАТЬ… остаётся сайтом. Тот же обрыв сразу после голого ИЗ / FROM / СОЕДИНЕНИЕ / JOIN ("...ИЗ" + Объект.Наименование + "...") - не сайт. Генерируемый BSL-код: литерал, в котором после снятия одного уровня | строка снова начинается с | (вложенный литерал), или в тексте есть Новый Запрос( - это текст модуля или правила, собираемый строкой, а не запрос; сайт не создается.

Макросы шаблона #Имя: токен вида #Соединения (не имя ВТ после ПОМЕСТИТЬ/ИЗ/…). Статический СтрЗаменить по " #Имя" подставляет литерал-замену; иначе сайт/query → unparsed («макрос не раскрыт»), не fail синтаксиса #. Литерал-замена - только 3-й аргумент того же вызова, строковый литерал сразу после образца. Если там выражение (СтрЗаменить(Запрос.Текст, "#ИМЯ", ИмяМетаданных)), макрос не раскрыт: раньше за замену брался следующий литерал процедуры ("Ссылка" из УстановитьПараметр), и получался Справочник.Ссылка КАКСсылка.

Именованные слоты %Имя%: токен %Идентификатор% вне двойных кавычек текста запроса (имя не с цифры: %1 - это слот СтрШаблон). Если в любой процедуре того же модуля есть СтрЗаменить(..., "%Имя%", "литерал") - подставляется первая непустая литерал-замена (иначе пустая), в трассе помечается (модуль); так компилируется запрос, у которого условия дописывает соседняя процедура. Если литеральной замены нет - unparsed («Именованный плейсхолдер «%Имя%» подставляется в рантайме»), не fail. СтрЗаменить с образцом %Имя% и выражением в 3-м аргументе - динамический плейсхолдер, как &... / //....

Слоты СтрШаблон %1…%n: в литерале-запросе вне двойных кавычек (не LIKE "%1%") подставляется пустая строка — как вызов с пустым доп. условием, но только для необязательного слота: хвост после операнда перед концом предложения (... NULL %1 ОБЪЕДИНИТЬ, ... = 1 %1 в конце текста, перед ; / ) / УПОРЯДОЧИТЬ / СГРУППИРОВАТЬ / ИМЕЮЩИЕ / ИТОГИ / ИНДЕКСИРОВАТЬ / ДЛЯ) или пустой параметр виртуальной таблицы (, %1)). После такой подстановки сайт остаётся parsed и идёт в компиляцию. Любой другой слот задает структуру запроса (имя таблицы, список полей, операнд после И / ГДЕ / ИЗ, %1.%2.%3): пустая строка ломает текст, поэтому сайт unparsed «Слот СтрШаблон %N задает структуру запроса и подставляется в рантайме» (например, Из %1.%2.%3, ВЫБРАТЬ ПЕРВЫЕ 2 %1 ИЗ, И %1). Зеркало: форма EPF (СтруктурныйСлотСтрШаблона), harvest_query_sites.py (structural_strshablon_slot); ObjectModule подставляет пустые слоты только как страховку для старых sites.json.

Статическая законченность собранного текста. Текст, который собран из литералов, но остается головой конкатенации или содержит текстовый макрос, не компилируется, и ошибка платформы была бы ложной. Проверки выполняются по токенам вне строковых литералов и комментариев // (форма ТокеныКодаЗапроса, Python query_code_tokens), после подстановки слотов:

  • текст не начинается с ВЫБРАТЬ / SELECT / УНИЧТОЖИТЬ / DROP - это фрагмент условия, вклеиваемый в запрос в другом месте, сайт не создается (например, УсловияЗапроса.Добавить("Договоры.Организация В (ВЫБРАТЬ ...)") или "Распоряжение В (ВЫБРАТЬ ...)");
  • текстовый макрос вместо параметра: КАК Алиас, &ДополнительныеСоединения перед ГДЕ / СГРУППИРОВАТЬ / ; / концом текста, два параметра подряд &Условие1 &Условие2, имя таблицы в скобках Документ.[ИмяДокумента] - unparsed «Текстовый макрос «...» подставляется в рантайме» (кейсы СкладыСервер.ОформитьПриобретениеТоваровПоДвухходовке, ДоговорыКонтрагентов.ОбработкаПолученияДанныхВыбора);
  • литерал обрывается: последний токен - ГДЕ / И / ИЛИ / НЕ / ПОМЕСТИТЬ / УНИЧТОЖИТЬ / ПО / КАК / ВЫБРАТЬ / ИЗ / В / ТОГДА и другие слова, ждущие операнд, или , / ( / . / =, либо открытых скобок больше, чем закрытых, либо после последнего СОЕДИНЕНИЕ / JOIN нет ПО / ON (функция возвращает шаблон, условие соединения дописывает вызывающий код), - unparsed «Текст запроса дописывается в рантайме (литерал обрывается на «...»)» (кейсы "УНИЧТОЖИТЬ " + ИмяТаблицы, "ВЫБРАТЬ * ПОМЕСТИТЬ " + ИмяВТ, "...ГДЕ" + условия, "...ГДЕ Периоды." + ТекстРодитель, незакрытый подзапрос).

Зеркало: форма EPF (ЭтоТекстБезКорняЗапроса, ТекстовыйМакросРантайма, ОборванныйХвостТекстаЗапроса), harvest_query_sites.py (is_non_root_text, runtime_text_macro, truncated_query_tail). Правила проверены на рабочей конфигурации 29.09.2026: все срабатывания пришлись на сайты, которые раньше давали ложный fail, и на один литерал, реально обрывающийся на запятой.

Статический СтрЗаменить: если в теле есть замена куска полного (корневого) запроса на фрагмент с ПОМЕСТИТЬ или на склеечный фрагмент (ОБЪЕДИНИТЬ / СОЕДИНЕНИЕ / предикат / хвост ;), harvest собирает один итоговый текст только когда 2-й и 3-й аргументы СтрЗаменить — чистые строковые литералы (явная пара), либо по эвристике префикса до ПОМЕСТИТЬ. Перед склейкой и образец, и замена проходят то же снятие ведущего | продолжения строк 1С, что и корневые литералы (СклеитьПродолжениеСтроки1С / Python glue_bsl_line_continuations) — иначе в итоговый текст попадает литерал | И / | ИЛИ (ложный fail, кейс ПриходныйОрдерНаТовары / &УсловиеПоПараметруПолитикиУчетаСерий). Литерал, который стоит 2-м аргументом явной пары (образец поиска), сам сайтом не становится: СтрЗаменить(ТекстИзФункции(), "ВЫБРАТЬ", "ВЫБРАТЬ ПЕРВЫЕ 1") раньше давал сайт ВЫБРАТЬ ПЕРВЫЕ 1. После 3-го литерала до ) допускаются только пробелы: "…" + Выражение (например "ВЫБРАТЬ ПЕРВЫЕ " + Строка(…)) пару не даёт. MUST NOT подставлять glue-литералы (И , И НЕ , …) вместо произвольных коротких строк тела (имена УстановитьПараметр("Организация", …) и т.п.) — иначе картезиан мутантов. Если 3-й аргумент — выражение (Условие + "…" / ?(…)), статическая склейка этой пары не делается; литерал-замену (фрагмент) без базы не компилируют (P0+). Если в корне остаётся образец динамического СтрЗаменить вида &… / //… / %Имя% / %Имя / [Имя] — сайт unparsed («подставляется в рантайме»), не fail. Голое имя или .Имя в роли такого образца на месте имени объекта после класса таблицы (Справочник.ИмяМетаданных КАК Т + СтрЗаменить(Запрос.Текст, "ИмяМетаданных", ИмяСправочника)) статус не меняет: сайт остается parsed с полем runtimeTableName и компилируется. Если компиляция падает с ошибкой, называющей это имя (Таблица не найдена "Справочник.ИмяМетаданных"), статус unparsed («Имя таблицы «...» подставляется в рантайме»); если шаблон называет реальную таблицу (Документ.ТранспортнаяНакладная), он проверяется как обычный запрос (правило K5 в ALGORITHM.md). Если образец поиска не найден — только тихий пропуск фрагмента (P0+), без fail.

Статическая конкатенация (T8): "лит1" + "лит2" (и + Символы.ПС) одним выражением склеивается в один текст, даже если между частями закомментированные или пустые строки; части отдельными сайтами не становятся. Дошла до выражения рантайма - не склеивается (N4 / N5).

Список слов через | (F9): литерал, где вне кавычек | стоит между двумя символами идентификатора ("ВЫБРАТЬ|РАЗЛИЧНЫЕ|ПЕРВЫЕ|ИЗ" - ключевые слова подсветки кода), не запрос: сайта нет, и «Текст запроса не собран из литералов» он не дает. Так же обрабатывается запрос WQL к Windows (F10): после FROM стоит класс WMI Win32_… / CIM_… / MSFT_… / __… (Select * from Win32_NetworkAdapterConfiguration в ExecQuery).

Имя, оборванное выражением (F11): литерал, который кончается частью имени, а дальше через + идет переменная или Строка(...) / Формат(...) / XMLСтрока(...) ("ВЫБРАТЬ Наименование Из ВтУровень" + Строка(н)), отдельным сайтом не становится. Перед вызовом модуля (+ ОбщегоНазначения.РазделительПакетаЗапросов()) голова компилируется как раньше.

Одинаковые условия ветвления (T1b): две оси path-variant с одинаковым текстом условия (Если ПараметрыПечати.ВыводитьНаборы Тогда дважды) берут один и тот же рукав: путь «первая замена пропущена, вторая применена» не строится.

Обе руки Если/Иначе дописывают текст (T1c): если в каждой руке верхнеуровневого Если с Иначе стоит Х = "..." или Х = Х + "..." той же переменной, которую отслеживает path-variant, а позже безусловно дописывается кусок, начинающийся с И / ИЛИ (продолжение условия, которому нужен ГДЕ из рук), ось ветвления эти присваивания не моделирует (она применяет только литеральные СтрЗаменить), а путь «обе руки пропущены» невозможен: одна из рук выполняется всегда (... ИЗ Таблица и общий хвост И условие без ГДЕ). Сайт процедуры один: unparsed «Текст запроса дописывается внутри Если/Иначе: ветви дописывания не моделируются», литералы по отдельности не компилируются. Если без Иначе, Если/Иначе, где одна рука текст не трогает, и Если/Иначе без такого продолжения условия по-прежнему дают настоящий путь «пропустить» (границы правила - ALGORITHM.md, раздел 8). Зеркало: форма EPF (ЛевыеЧастиНаращиванияТекстаВИнтервале, ТекстыЗапросовПоВариантамПутей), harvest_query_sites.py (_span_text_growth, path_variant_query_texts). Поймано 09.10.2026 на ERP «Техно» (ВернутьСоставПериметра).

Хвост ОБЪЕДИНИТЬ ВСЕ с ИНДЕКСИРОВАТЬ ПО (P9): собранный текст, в пакете которого есть ИНДЕКСИРОВАТЬ ПО и нет ПОМЕСТИТЬ, сам запросом не является: это хвост цепочки, который код склеивает с головой в рантайме (разделитель берется из вызова функции, склейка литералов T8 до него не доходит). Сайт unparsed «Хвост ОБЪЕДИНИТЬ собран отдельно: «ИНДЕКСИРОВАТЬ ПО» без «ПОМЕСТИТЬ» в том же пакете запроса»; голова цепочки компилируется отдельно как обычный текст. Зеркало: форма EPF (ИндексироватьБезПоместить), harvest_query_sites.py (index_without_put). Поймано 09.10.2026 на ERP «Техно» (РаспределениеДоходовПоНаправлениямДеятельности.ТекстЗапросаТаблицаОборотыДоходовРасходов).

Образец СтрЗаменить с токеном &Имя (P2): образец, в котором &Имя стоит не только в начале (ВЫБРАТЬ &ВложенныйЗапрос), при третьем аргументе-выражении тоже считается динамическим плейсхолдером: слот под целый источник или список выбора, оставшийся в тексте, дает unparsed «Плейсхолдер «...» подставляется в рантайме». Зеркало: ЕстьТокенПараметраВОбразце, _has_parameter_token. Поймано 09.10.2026 на ERP «Техно» (ОтразитьЗаданияКРаспределениюРасчетовСКлиентами, ...СПоставщиками).

Path-variant (цепочки + ветки): если процедура собирает текст через ТекстЗапроса = "A" / ТекстЗапроса = ТекстЗапроса + "B" (или Запрос.Текст) и/или верхнеуровневые Если/Иначе с литеральными СтрЗаменить, harvest сначала строит варианты путей: склейка цепочки → произведение осей (Если без Иначе = применить/пропустить; Если/Иначе = взаимоисключающие руки) → на каждом пути все литеральные замены этой руки по порядку. Безусловный ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "лит", "лит") на верхнем уровне процедуры (вне Если, первый аргумент - та же переменная) применяется на каждом пути в своем месте цепочки (например, &ПоляСубконто после КонецЕсли). Каждая замена - по всем вхождениям, как настоящий СтрЗаменить. Конкатенация в присваивании: + "лит" и + Символы.ПС / Символы.Таб / Chars.LF склеиваются статически; + <выражение> ("...Справочник." + ИмяОбъекта + " КАК ...") делает цепочку динамической - сайт unparsed («Текст запроса собирается в рантайме (конкатенация с выражением «ИмяОбъекта»)»), статические половины не компилируются (кейс ОграничитьДанныеВыбора). Лимит путей 32; при превышении — до 32 обычных сайтов плюс один unparsed с ошибкой «превышен лимит путей ветвления». Одинаковые тексты дедуплятся. Для той же цепочки независимые P2-мутации половин (A-only / B-only) не эмитятся. Если path-variant не применим — остаётся узкий P2 выше. Не в scope: подстановка 3-го аргумента-выражения (ТекстЗапросаКоэффициентаУпаковки(...) и т.п.) — по-прежнему unparsed (follow-up «C»).

Нет литерала при присвоении Запрос.Текст = / ТекстЗапроса = или Новый Запрос(...) → unparsed «нет строкового литерала» (рантайм); упоминание без присвоения (Выполнить(ТекстЗапроса), имя области #Область ТекстЗапроса...) строки не дает. Если к литералу-корню приклеено выражение (Новый Запрос("ВЫБРАТЬ ... Документ." + ИмяТаблицы + " КАК Д")), причина - «конкатенация с выражением «ИмяТаблицы»» (N5). Запрос без источника (ВЫБРАТЬ &А КАК А, без ссылок Таблица.Поле вне ЗНАЧЕНИЕ(...)) - обычный запрос и компилируется (L5). Если в собираемой процедуре есть литерал, похожий на запрос, а ни один текст из него не собран, - unparsed «Текст запроса не собран из литералов»; молча процедура с запросом из отчета не пропадает. Неполный SQL (& как имя таблицы после ИЗ/СОЕДИНЕНИЕ/JOIN — «подстановка в рантайме»; #ИМЯ; незавершённый комментарий) → unparsed с причиной, не fail. ВТ, созданная в другом литерале той же процедуры, даёт needs-context — ожидаемо при раздельных сайтах.

Каталог исходников, каталог вендора, флаг и каталог расширений сохраняются в настройках пользователя ИБ (ХранилищеСистемныхНастроек, при закрытии формы).

2. Запас: Python harvest + COM

Нужны: Python 3, COM V83.COMConnector (битность = платформе 1С).

cd <каталог проекта>
copy compile_queries.params.example.json compile_queries.params.json
python .\harvest_query_sites.py --src "<исходники конфигурации>" --vendor "<исходники вендора того же релиза>" --out .\sites.json
powershell -File .\compile_queries.ps1 -SitesPath .\sites.json -ReportPath .\compile_report.json

--src (или --roots) и --vendor обязательны. Python-harvest, как и форма EPF, отбирает доработанный код сравнением с деревом вендора того же релиза, а не по маркерам (с 02.10.2026): процедура попадает в отбор, если ее нет у вендора или она отличается без учета пробельных символов (комментарии учитываются). Основание - в поле kind: differs, absent-in-vendor, module-absent-in-vendor (файла нет у вендора: собственный модуль, модуль, удаленный вендором, или сторонний продукт). Полные версии из Configuration.mdo / Configuration.xml должны совпадать, иначе прогон отказывает. Правила V1-V6 - ALGORITHM.md, раздел 3а. Время на конфигурации уровня ERP: около 2 мин 40 с. Живые compile_queries.params.json, sites.json, *_report.json в git не кладутся.

COM может обернуть ошибку платформы в NullReferenceException. Полный ОписаниеОшибки() — через EPF.

3. Запас: пакетный /C по готовому JSON

1cv8.exe ENTERPRISE /S"server:1541/infobase" /N"Администратор" /DisableStartupDialogs /Execute"<каталог проекта>\CompileQueryTexts.epf" /C"in=<каталог проекта>\calibrate_sites.json;out=<каталог проекта>\calibrate_report.json"

Без /C с in= и out= форма остаётся открытой (не завершает предприятие).

Калибровка оракула: синтетический ok + битое поле → fail + ОБЪЕДИНИТЬ ВСЕ с разным числом полей → fail. Кейсы K6 (КалибровкаВТ..., КалибровкаПодзапрос..., КалибровкаБитоеПолеМетаданныхРядомСВТ; таблица ВнешняяВТ в запросе нигде не создается): поле внешней таблицы по псевдониму, без псевдонима, через реквизит, через созданную из нее таблицу, через вложенный запрос и в условии подзапроса - needs-context; нет колонки у созданной в тексте таблицы или вложенного запроса и битое поле таблицы метаданных рядом с внешней - fail. Кейсы повтора схемы (kind = calibrate-schema-*, база - Справочник.Валюты, нужна ИБ с этим справочником) и ожидаемые исходы: schema_ok - ok, поле добавлено; schema_fail_block - fail/block (битое поле бросает сразу); schema_fail_compile - fail/compile (итог ... ГДЕ ГДЕ); schema_no_effect_exit (выход по исключению выхода), schema_no_effect_not_assigned (итог не присвоен), schema_no_effect_same (итог равен базовому), schema_no_effect_alias (нет ожидаемого псевдонима) - ok + schemaNoEffect с соответствующей причиной; schema_needs_context_variable (Форма) и schema_needs_context_function (неизвестная функция) - needs-context с именем; schema_forbidden_word (Записать) - unparsed; schema_extension_not_applied (root = несуществующее расширение, битое поле) - needs-context + extensionNotApplied. Вклад и инварианты: schema_contribution (одна колонка), schema_contribution_full (колонка, источник, соединение, отбор), schema_contribution_package (+ добавлен пакет 1), schema_text_replace_ok (правка текстом СтрЗаменить) - ok + schemaContribution, без schemaInvariant; schema_invariant_i1 (mainTable другого справочника), schema_invariant_i2 (блок читает ПакетЗапросов[1], правит [0]), schema_invariant_i3 (динамический список, колонка в пакете ПОМЕСТИТЬ), schema_invariant_i4 (образец СтрЗаменить отсутствует; вклад пуст) - ok + schemaInvariant с соответствующим номером; schema_no_effect_empty_contribution (текст отличается пробелами, структура та же) - ok + schemaNoEffect "вклад доработки пуст". Для кейса И1 и schema_contribution_full в ИБ нужен справочник Контрагенты. Если битый кейс даёт ok — оракул слишком мягкий, .Выполнить() не включать.

Калибровка harvest: python .\calibrate_harvest.py прогоняет пару calibrate_harvest\ (наше) + calibrate_harvest_vendor\ (вендор, та же версия) и мини-расширение calibrate_extensions\Калибровка_Расш\: основание и разбор по каждой процедуре, признаки T7 / T7b; код возврата 1 при расхождении. python .\calibrate_parity.py (нужен OneScript) сверяет код формы с Python: по модулям и сквозным прогоном клиентского конвейера формы (ALGORITHM.md, раздел 9). Та же пара, открытая в форме EPF (исходники calibrate_harvest, вендор calibrate_harvest_vendor, формат Конфигуратор, без выгрузки), должна дать те же процедуры с тем же Основанием.

В JSON-отчёте счётчик внешней ВТ — ключ counts.needsContext (не needs-context). Строка status у сайта по-прежнему needs-context. У элемента есть query.

Поле временной таблицы, созданной вне текста (K6): ошибка платформы «Поле не найдено "A.X..."» или «Поле не входит в группу "A.X..."» не считается дефектом текста (needs-context, в ошибке впереди «Временная таблица «Имя» создается вне текста запроса: ...»), если A - псевдоним источника, который является временной таблицей (имя без точки), не созданной ПОМЕСТИТЬ в более раннем пакете этого же текста. Источник ищется по позиции ошибки {(строка, колонка)} в самом внутреннем подзапросе (иначе в пакете), псевдоним сопоставляется по тексту (ИЗ / СОЕДИНЕНИЕ <источник> [КАК] <псевдоним>, регистр не учитывается; без псевдонима источник доступен по своему имени). Расширения правила: (1) путь поля через реквизит (A.Колонка.Реквизит, три и больше имен) объясняется и временной таблицей, созданной в тексте из внешней таблицы, а также вложенным запросом, в котором есть внешняя таблица, потому что тип колонки известен только по внешним данным; (2) поле без псевдонима (Поле, Колонка.Реквизит, которому нет псевдонима) объясняется, если все источники области - временные таблицы и хотя бы одна внешняя. Остается fail: битое поле у источника-таблицы метаданных (в том числе рядом с внешней таблицей), колонка, которой нет у таблицы, созданной в тексте, колонка, которой нет у вложенного запроса (без пути через реквизит). Нет позиции ошибки в тексте - правило не срабатывает. Зеркало: ОМ ВнешняяВТПоОшибкеПоля, COM Get-ExternalTempTableForFieldError; COM теряет текст платформенной ошибки (NullReferenceException), поэтому в COM-пути правило на практике не срабатывает - только в EPF (форма и пакетный /C). Поймано 09.10.2026 на ERP «Техно» 2.5.27.75: 21 ложный fail из 167.

Трасса подстановок replaces: у сайта в sites.json и у элемента отчета (EPF и COM) - массив строк "образец" -> "замена" (в одну строку, до 80 символов на сторону): какие СтрЗаменить, макросы #Имя, слоты %Имя% ((модуль)) и %1..%n harvest применил к этому тексту. Поле есть, только если подстановки были. В форме показывается в поле под таблицей вместе с ошибкой. Спор «ошибка инструмента или кода» решается по трассе: видно, какая именно замена дала текст.

Холостая замена noopReplace (правило T7): у сайта (форма EPF и sites.json) и у элемента отчета (EPF, /C, compile_queries.ps1) - массив пар "образец" -> "замена", образец которых не найден ни в одном тексте переменной: доработка правит текст, в котором опорной точки больше нет. Статус компиляции не меняется (платформа текст принимает - строка ok). Признак ставится только там, где текст переменной целиком собран из литералов этой процедуры; если текст пришел из функции или параметра, harvest ответа не дает. Подробные условия - ALGORITHM.md, T7.

Вероятно холостая замена noopReplaceLikely (T7b, оба пути): для пар, на которые T7 не отвечает (текст из функции или параметра), harvest ищет однострочный образец во всем дереве исходников. Если он встречается только в самих вызовах СтрЗаменить, которые его ищут, - пара попадает в noopReplaceLikely. Сигнал слабее noopReplace: образец может собираться конкатенацией в рантайме, проверять вручную. Поиск по дереву добавляет к прогону Python около 25 с; в форме это отдельная фаза после сбора.

Правка через СхемаЗапроса schemaPostProcess (T9, оба пути): если в процедуре есть вызов УстановитьТекстЗапроса(...), каждый ее сайт получает массив таких вызовов ("УстановитьТекстЗапроса(Запрос.Текст)"). Компилируется текст из литералов, а то, что код потом добавляет через схему (поля, отборы, источники), статически не проверяется: ok относится только к базовому тексту. Статус признак не меняет; в форме к статусу дописывается «правка схемой», такие строки видны и при «Только ошибки», расшифровка - в поле под таблицей. Признак ставится на всю процедуру, без привязки к переменной. Запрос, собранный схемой с нуля (Новый СхемаЗапроса без УстановитьТекстЗапроса), дает unparsed «Текст запроса собирается через СхемаЗапроса, литерала нет» (N6). Не видно: схема во вспомогательной функции, которой текст передан параметром (ПолучитьТекстЗапросаПоместитьПоследний(ТекстЗапроса, ...)) - вызывающая процедура признака не получает.

Повтор блока СхемаЗапроса parse="schema-replay" (ALGORITHM.md, раздел 4а): для собственного модуля расширения или самой основной конфигурации (этап 7а: проверяемое дерево - его же основное, вендорские изменения приходят в него при обновлении), где код правит запрос вендора через Новый СхемаЗапроса -> УстановитьТекстЗапроса(...) -> изменения -> ПолучитьТекстЗапроса(), harvest выдает один сайт schema-replay вместо строк unparsed (N1/N2/N6) той же процедуры. Базовый текст берется не из расширения, а из основной конфигурации: в форме, которой принадлежит модуль, ищется реквизит из входа <Реквизит>.ТекстЗапроса (QueryText и MainTable в Ext/Form.xml Конфигуратора или Form.form EDT); для проверки нужен каталог основной конфигурации (первый --src / первый каталог формы). Если вендор ставит текст списка кодом (литерал в функции общего модуля), базовым становится этот литерал (baseFromVendorCode). Поля сайта: schemaInput (выражение входа блока), schemaBlock (блок, переписанный под песочницу: вход ИсходныйТекст__, схема Схема__, выход ИтоговыйТекст__, Возврат; -> исключение выхода), baseText, baseSource (файл#реквизит или Модуль.Функция), mainTable, expected (литеральные псевдонимы, которые блок присваивает). Флаги: vendorAltersBaseText (код формы вендора сам меняет текст списка - базовый текст может не совпадать с рантаймом), baseFromVendorCode, precedingGuard (до блока есть Возврат), conditionalBlock (выход внутри Если/Цикл). Блок с недопустимым вызовом (вне белого списка, Выполнить, Попытка и др.) или без определимого базового текста остается unparsed с причиной. Три источника базового текста: (R5) реквизит динамического списка формы, которой принадлежит модуль; (R5c) блок в ManagerModule.bsl с входом Форма.<Реквизит>.ТекстЗапроса - harvest ищет в общих модулях расширения хук &После("ПриСозданииНаСервере") с ветками Если ИмяФормы = "Вид.Объект.Форма.Имя", вызывающими процедуры менеджера этого объекта с передачей формы (до 3 звеньев вверх от процедуры с блоком), и выдает по сайту на каждую форму ветки (replayForm, свой baseSource и базовый текст - реквизит формы в основной конфигурации; у формы без реквизита - unparsed с причиной); (R5b) блок в ObjectModule.bsl отчета с входом-переменной, присвоенной из СхемаКомпоновкиДанных.НаборыДанных.<Набор>.Запрос, - запрос набора в основном макете СКД отчета (Templates/ОсновнаяСхемаКомпоновкиДанных; сначала в дереве расширения, затем в основной конфигурации), mainTable пусто, schemaInput = выражение набора. Для основной конфигурации и расширения добавлено: (R5c-direct) блок в ManagerModule.bsl достигается не только хуком расширения, но и прямым вызовом <Коллекция>.<Объект>.<Процедура>(ЭтотОбъект | ЭтаФорма, ...) из модуля формы того же объекта (также через переменную, которой присвоен <Коллекция>.<Объект> или ОбщегоНазначения.МенеджерОбъектаПоПолномуИмени("Вид.Объект"); цепочка через промежуточные процедуры менеджера до 3 звеньев) - сайт на каждую вызывающую форму (replayForm); у основной конфигурации хуков нет, поиск идет только по прямым вызовам; (алиас входа) Т = [Форма.]Реквизит.ТекстЗапроса; ... УстановитьТекстЗапроса(Т) - вход блока раскрывается до выражения; [Форма.]Реквизит.ОсновнаяТаблица в блоке заменяется литералом главной таблицы реквизита; (несколько блоков в процедуре) независимые блоки дают по сайту с schemaBlockIndex (0, 1...), блок, вход которого равен выводу предыдущего блока той же прямой последовательности, сливается с ним в один сайт с последовательным кодом. Хвост блока (R1b): операторы после блока вида <вывод> = СтрЗаменить(<вывод>, ...) (и присваивания простым переменным для них) входят в блок, если проходят тот же белый список; первый оператор хвоста, не прошедший белый список (например, замена берет текст из функции), заканчивает блок, и если после него остались замены, сайт получает schemaTailOmitted (хвост не выполнялся, проверить вручную; в форме - строка в поле под таблицей). Разрешение цепочки R5c - только по тексту: хук в модуле формы расширения и вызов менеджера через промежуточный общий модуль не разрешаются. Блок выполняет модуль объекта EPF (форма и пакетный /C) над baseText в песочнице (ВыполнитьБлокСхемы: безопасный режим, транзакция с безусловной отменой, локальные только служебные __); итог компилируется тем же оракулом, что и обычные сайты. Классы исхода (статус / поле строки отчета):

  • блок обратился к имени вне песочницы («Переменная не определена (X)», «Процедура или функция с указанным именем не определена (X)») - needs-context, в error имя X;
  • стоп-слово в блоке (Выполнить, Записать, Попытка, ПолучитьФорму, COMОбъект и др.; список - ЗапрещенноеСловоБлокаСхемы) - unparsed «блок схемы содержит недопустимый вызов: X», блок не выполняется;
  • другое исключение блока (битое поле, неверный индекс) - fail, failKind = block, в error текст платформы;
  • итог не компилируется - fail, failKind = compile; не компилируется эталон (блок итог не присвоил) - fail, failKind = base;
  • иначе статус компиляции итога (ok) и, если доработка не сработала, признак schemaNoEffect (не статус, строка видна и при "Только ошибки"; в форме к статусу дописывается "доработка схемой не сработала"). У любого сайта повтора к статусу в форме дописывается "повтор схемы". Причины через "; ": "итог не присвоен (выход из блока)", "итог не присвоен" (тогда компилируется эталон - базовый текст после прохода через СхемаЗапроса), "итог равен базовому тексту", "нет ожидаемого поля X" (псевдонима из expected нет среди колонок ни одного пакета итога, регистр не учитывается);
  • extensionNotApplied: имя расширения (поле root сайта, имя подпапки каталога расширений) не найдено среди РасширенияКонфигурации или расширение не активно - fail подавляется, статус needs-context, в error приставка "Расширение X не применено в ИБ". У сайтов основной конфигурации и сайтов без root проверки нет.
  • schemaContribution (массив строк, как replaces): вклад доработки - разность схемы после блока и эталонной схемы (Новый СхемаЗапроса + УстановитьТекстЗапроса(baseText)) по пакетам и операторам (индексы с нуля, только ЗапросВыбораСхемыЗапроса): + колонка Валюты.Наименование КАК Наименование (пакет 0, оператор 0); виды отпечатков - колонка <выражение> КАК <псевдоним>, источник <таблица> КАК <псевдоним>, соединение <тип> <псевдоним> ПО <условие>, отбор <выражение>, группировка <выражение>; "+" добавлено, "-" исчезло, новый пакет - + добавлен пакет N. Считается по схеме итога (Схема__, если ее текст равен итогу, иначе по схеме, разобранной из текста итога) и только если итог присвоен. Пустой вклад добавляет к schemaNoEffect причину "вклад доработки пуст" (если уже есть "итог равен базовому тексту", не дублируется).
  • schemaInvariant (массив строк "И1: ..." - "И4: ..."): нарушение инварианта позиции правки, признак как schemaNoEffect, статус не меняет (в форме к статусу дописывается "нарушен инвариант схемы", строка видна и при "Только ошибки" и в СКД "требует внимания"). И1 - оператор с вкладом выбирает из mainTable (первый источник, виды метаданных в обоих написаниях); И2 - пакеты и операторы, к которым блок обращается литеральным индексом (ПакетЗапросов[N], Операторы[N]; Количество() - 1 = последний пакет), совпадают с получившими вклад; И3 - для динамического списка (schemaInput оканчивается на ТекстЗапроса, baseSource содержит # или есть baseFromVendorCode) колонки добавлены в выводящий пакет (последний выбор без ПОМЕСТИТЬ); И4 - образец второго аргумента СтрЗаменить блока есть в эталонном или базовом тексте (или в замене другого вызова блока). Правила - ALGORITHM.md, раздел 4а, подраздел "Вклад доработки и инварианты".

Флаги сайта (precedingGuard, conditionalBlock, vendorAltersBaseText, baseFromVendorCode), baseSource, mainTable, expected переносятся в строку отчета как есть; в форме они выводятся в поле под таблицей. Временный error harvest в результате не остается. COM-путь (compile_queries.ps1) блоки не выполняет: такие сайты получают unparsed "Повтор блока СхемаЗапроса выполняется только формой или пакетным /C (COM не выполняет код блока)".

Свертка вариантов: строки отчета одной процедуры (root + rel + proc) с одинаковым неуспешным status и одинаковым error (варианты путей ветвления, падающие в одной точке) сворачиваются в одну; у нее поле variants = число вариантов (есть только при > 1), query и replaces - первого варианта. Счетчики counts / countsByRoot и «всего» считаются после свертки. Успешные строки не сворачиваются. В COM-пути у unparsed теперь тоже есть error (причина из harvest).

Замер этапов (форма EPF): после компиляции форма выводит сообщением время прогона по этапам: сбор (файлов, чтение с диска, разбор, раскрытие папок), поиск образцов T7b (файлов, чтение с диска, подсчет, подготовка), компиляция (время на сервере из compileMs и ожидание на клиенте - БСП опрашивает фоновое задание с растущим интервалом, поэтому ожидание длиннее), паузы между тиками. Нужен, чтобы выбирать ускорение по цифрам: долгий разбор - кандидат на сбор Python-зеркалом, долгое чтение - на кэш между прогонами. В отчете (СкомпилироватьСайты, пути EPF и /C) - поле compileMs, время компиляции в мс.

Массовый прогон всех сайтов — по команде оператора, не часть apply. Живой полный dump рабочей базы — тоже только по явной команде (монополия Конфигуратора).