---
name: onec-testbench-skill
description: "Скилл 1c-testbench — стенд проверки доработок 1С (компиляция платформой через расширение, BSL LS, сверка метаданных, карты форм)"
metadata: 
  node_type: memory
  type: reference
  originSessionId: cb6702f0-03d5-4dee-8c14-be01772a7808
  modified: 2026-08-14T19:20:37.435Z
---

Скилл **`1c-testbench`** (`~/.claude/skills/1c-testbench/`) — проверка доработок 1С
ПЕРЕД сдачей. Собран 2026-07-30, все слои проверены прогонами на реальной
`КомиссияКлиентов.epf` (4 формы, 3 макета). Дополняет [[onec-epf-skill]]
(тот собирает и правит — этот проверяет).

**Вход:** `tools/tb.py check <.epf|каталог выгрузки>` → exit 0/1, ~60 с полный
прогон. Флаги `--fast` (без платформы, ~20 с), `--no-render`, `--json`.

**Слои:** L0 `gates.py` (лексер литералов BSL: одиночная кавычка в тексте
запроса, незакрытый литерал, баланс Функция/КонецФункции, NFC, BOM) · L0
`lint_bsl.py` (BSL LS 1.0.6, блокирует только ParseError — иначе legacy не
пройдёт) · L0.5 `meta_check.py` (имена таблиц/полей из текстов запросов
БСЛ И `<query>` схем СКД в `.erf`, включая UNION — против индекса
конфигурации, 5714 объектов, индекс строится 3 с) · L0.5 `mxl_check.py`
(имена макетов/областей `ПолучитьМакет`/`ПолучитьОбласть` против
`Templates/`; `.mxl` уже приходит от платформы как читаемый XML
`data/spreadsheet`, парсить бинарник не нужно) · L1 компиляция платформой
(`.epf` И `.erf` — `ext_wrap.py` различает `ExternalDataProcessor`/
`ExternalReport` автоматически) · L3a `render_form.py` (HTML-карта формы).

**Важно: `/CheckModules` не проверяет схему СКД и имена макетов вообще** —
опечатка в `Документ.X` внутри `<query>` СКД или в `ПолучитьОбласть("Y")`
проходит платформенную компиляцию чисто (проверено эмпирически). Это не
пробел стенда, а реальная слепая зона платформы — L0.5 meta/mxl единственные
слои, которые это ловят.

**Побочная находка (2026-07-30):** реальный `~/Desktop/
ПродажиПоКонтрагентам.erf` (файл от 2026-06-10) ссылается в схеме СКД на
`Документ.ЗаказПокупателя`, а в текущей НоваяКонфа документ переименован в
`ЗаказПокупателя2` — отчёт сломан относительно актуальной конфигурации,
платформа этого не видит.

**Главное открытие: внешнюю обработку МОЖНО скомпилировать платформой** —
`ext_wrap.py` оформляет её объектом расширения и вызывается
`/CheckModules -Extension <имя>`. Отменяет прежнее «функционально/синтаксически
проверить нельзя» из [[onec-epf-no-1c-runtime]]. Тупики (НЕ повторять):
`POST /build` даёт HTTP 200 на некомпилирующемся коде; `/CheckConfig
-ExternalReportsOrDataProcessors` файл из каталога не смотрит (логи чистой и
битой совпали побайтово); `/CheckConfig -Extension` модули расширения не
проверяет — нужен именно `/CheckModules`.

**Регрессионный корпус** `tests/run_corpus.py` — 13 кейсов на ДВУХ базах
(`.epf` и `.erf`), каждый = реальная грабля: кавычка в модуле объекта и в
модуле формы, потерянный КонецФункции, опечатка в имени документа,
переименованный реквизит, лишний аргумент, опечатка в `<query>` СКД,
опечатка/регистр/комментарий в макетах, плюс чистые контроли. Каждый новый
инцидент в проде → новый кейс. Полная таблица «ловит / не ловит / тупики» —
`reference/limits.md`.

**Прогон на 2 крупных реальных файлах (2026-07-30) вскрыл 4 бага в САМОМ
стенде** (не в проверяемых обработках) — платформа и BSL LS оба говорили
«чисто» на том же коде, что и указало на проблему в чекерах: (1) gates.py
ломался на маркере-комментарии `//+ Бурдуков...` ВНУТРИ текста запроса
(легальная практика, 23 ложных находки на одном файле); (2)(3) meta_check.py
был регистрозависим для имён полей (`Т.ссылка` vs `Т.Ссылка`) и не знал
стандартных полей `РегистрБухгалтерии`; (4) mxl_check.py та же
регистрозависимость для областей + не пропускал закомментированный код. Все
4 исправлены и закреплены кейсами. Урок: гонять стенд НЕ только на маленьких
эталонах — крупный/сложный реальный файл ловит баги самого инструмента,
которые маленький эталон не воспроизводит.

**Подтверждено полным прогоном с реальной платформой: 13/13 (2026-07-30).**
Первый полный прогон (без `--fast`) дал 2 ложных провала на контрольных
кейсах `mxl_case_ok`/`mxl_comment_ok` — но баг оказался не в `mxl_check.py`,
а в самих тестовых кейсах `run_corpus.py`: они звали
`Обработка.ПолучитьМакет(...)` в модуле формы без предварительного
`Обработка = РеквизитФормыВЗначение("Объект");` (так реально делает
продовый код) — платформа справедливо ловила необъявленную переменную.
Исправлено добавлением недостающей строки в 3 кейса. Второй урок: расхождение
`--fast` vs полного прогона именно на «_ok»-контролях — сигнал искать баг
в тексте самого кейса, а не спешить чинить проверяемый слой стенда.
Подробности — `reference/limits.md`.

**Принцип: только программно, без интерактива.** Толстый клиент `1cv8
ENTERPRISE` на этой машине рендерится ЧИСТО (проверено 2026-07-30 — прежнее
«криво» было ошибкой), но стенд им никогда не управляет кликами —
Accessibility-автоматизация не введена, см. [[e2e_prod_context_relay_pattern]].
За реальными прод-данными (в дев-базе их нет, только конфигурация) — не
подключение агента к проду, а `tools/context_relay.py` (request → юзер сам
гоняет запрос в проде → ingest в `sandbox/data/`), изоляция описана в
`sandbox/README.md`.

**Локальные синтетические данные — тупик, исследован в 2 раунда, закрыт
окончательно.** Раунд 1: `/Execute` упирается в несъёмный диалог
«Предупреждение безопасности». Раунд 2 (2026-07-30): попытка обойти через
создание пользователя ИБ с `ЗащитаОтОпасныхДействий=Ложь` дошла до реального
создания пользователя, но упёрлась в НАКОПЛЕНИЕ независимых стопоров: диалог
входа неустраним через `/N`/`/P` даже в задокументированном синтаксисе
(платформенный баг или недокументированное поведение), пустой пароль
отклоняется политикой при входе, а форма с верно объявленным
`OnCreateAtServer` не инициализирует жизненный цикл под `/Execute` (открылась,
но событие не сработало — точный контракт `/Execute` нигде не документирован).
`ib_stand` возвращена в чистое состояние, основной конвейер не пострадал.
Полное расследование — `reference/limits.md`. НЕ повторять этот путь без
принципиально новой зацепки.

Требования: Java 21 (`/opt/homebrew/opt/openjdk@21/bin/java`, BSL LS 1.0.6 =
class file 65, Java 17 падает), стенд-база `ib_stand` = копия
`ib_u1005954706` (Конфигуратор не параллелится на одной базе), карты форм
отдаются dev-сервером `1c-form-maps` порт 8911 из `~/Разработки/.tb_preview`
(превью-панель не даёт DOM для `file://`). Разовая подготовка —
`tools/setup.sh`.

**Второй хост, подтверждён полностью рабочим (2026-08-14 вечер).** Скилл
синхронизирован на mac mini (`~/.claude/skills/1c-testbench/`, rsync с этого
Mac) — mini теперь единственная машина с 1С для устройств без своей платформы
(телефон и т.п.), см. [[onec_epf_no_1c_runtime]] и `~/.gemini/GEMINI.md` §5.1b.
Java 21 доустановлена (`brew install openjdk@21` на mini), BSL LS jar скопирован
напрямую с этого Mac (не через `gh-proxy` — из РФ душит до <1 КБ/с). Лицензия
1С активирована владельцем интерактивно (`1cv8 ENTERPRISE` под Rosetta на
mini). **Полный `run_corpus.py`: 13/13 и на mini, и на этом Mac** — оба хоста
синхронно проверены после найденного ниже бага.

**Найден и исправлен реальный баг `mxl_check.py` — NFC/NFD (2026-08-14).**
Прогон 13/13 на mini сначала дал 4 ложных срабатывания (`clean`,
`comment_marker_in_query`, `mxl_case_ok`, `mxl_comment_ok` — все контроли без
дефекта). Причина НЕ в кэше и НЕ в окружении mini: `index_templates()` брал имя
макета из ИМЕНИ КАТАЛОГА на диске (обычно NFD — кириллица разложена на
буква+диакритика, как обычно и отдаёт macOS-выгрузка), а `ПолучитьМакет("X")`
в коде сравнивался как есть (NFC, набран в редакторе) — визуально одинаковые
строки не совпадали побайтово, макет считался «несуществующим». На этом Mac баг
не проявлялся раньше просто потому, что конкретная выгрузка `dump-epf` тогда
дала NFC-имена — совпадение окружения, а не отсутствие бага. Фикс: нормализация
`unicodedata.normalize("NFC", ...)` и на имени каталога в `index_templates()`,
и на строковых литералах в `check_file()` перед сравнением. Исправлено в
`tools/mxl_check.py` на обоих хостах, проверено регрессией 13/13 на обоих.
Урок (продолжает урок 2026-07-30 про баги самого стенда): расхождение
результатов ОДНОГО и того же кода на разных машинах — это не повод искать
причину в окружении/лицензии/кэше в первую очередь, если сам код идентичен
(`diff` пуст) — искать нормализацию/кодировку данных, которые код обрабатывает.
