From 88ff37360663f4e894f8fa6c4985f93dfb1283ac Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 17 Sep 2026 22:25:16 +0600 Subject: [PATCH] [2026-09-17] eagle: personal/projects/zont-config-compiler.md --- personal/projects/zont-config-compiler.md | 164 ++++++++++++++++++++-- 1 file changed, 156 insertions(+), 8 deletions(-) diff --git a/personal/projects/zont-config-compiler.md b/personal/projects/zont-config-compiler.md index baa3f10f..78cc7de9 100644 --- a/personal/projects/zont-config-compiler.md +++ b/personal/projects/zont-config-compiler.md @@ -3,7 +3,7 @@ title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml namespace: personal type: how-to created: '2026-09-17' -updated: '2026-09-17m' +updated: '2026-09-17k30' tags: - personal - zont @@ -223,6 +223,20 @@ python3 test_roundtrip.py zont_config/config_X.txt > ✅ **СТАТУС: закрыта, round-trip байт-в-байт чистый.** `661 → 661` объектов, `686 → 686` строк, `diff` = 0. +### 5.0. 🔴 Pickle — мини-язык ZONT + +Тела объектов типа 59 — это **Pickle**, стековый мини-язык контроллера. Alex: «это Команда Pickle "3"». + +| Тело в конфиге | YAML | Примечание | +|---|---|---| +| `'set varname'` | `set_var: {…}` | см. §10.9, четыре формы цели | +| `'storeev I "<текст>"'` | `storeenv: {level: info, text: …}` | `I`→info, `A`→alert, `W`→warning, `E`→error | +| `'puts "<текст>"'` | `log: <текст>` | плоский ключ, без `args` | +| `'3'` (голый литерал) | `pickle: 3` | **числом**, не строкой (подтверждено Alex) | +| `'objcmd ""'` | ⏳ `descr` + `args` | форма вызова **не согласована** — см. §13 | +| `'expr "%0 + %1"'` | `expr: '%0 + %1'` + `args: [операнды]` | операнды — id объектов | +| `'objstate 0 0'` | `type: objstate` + `object` | | + ### 5.1. Trigger-сценарий `steps` состоит **ровно из одного шага типа 46** — условие **переносится** в `trigger:` наверх, @@ -575,7 +589,7 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z=<тип>' | 2 | 🔴 Пустой `raw` у типов 0/36 → **молчаливые дефолты** | Не трогать `raw*`-поля | | 3 | 🔴 Правка `#S…` в YAML бессмысленна — они `raw_payload` | Системные настройки — только через UI | | 4 | YAML на входе — строго **UTF-8** | Править YAML в UTF-8, `.txt` не пересохранять | -| 5 | 🔴 **Гонять конвертер в ЦЕЛЕВОЙ файл, а не в `/tmp`** | Проверка в `/tmp` не проверяет артефакт. Alex видел `type: trigger` в файле, который я не перегенерировал | +| 5 | 🔴 **Гонять конвертер в ЦЕЛЕВОЙ файл, а не в `/tmp`** | Проверка в `/tmp` не проверяет артефакт. Alex видел `type: trigger` в файле, который я не перегенерировал. 🔴 **Повторено 2026-09-17 (круг 30):** правки `storeenv`/`log`/`flag` ушли в круг через `/tmp`, а `zont_config/config_local_2026-09-17_21-13-18.yml` на диске остался старым → Alex дважды видел `descr: puts "Отладка"` и был прав, что «нихуя не поменялось». **Правило: после каждой правки — `python3 config-to-yml.py .txt > .yml` в тот же каталог и `grep` по нему, а не по выводу в `/tmp`** | | 6 | 🔴 **Не отдавать артефакт, не прочитав его самому** | Round-trip «байты сходятся» ≠ «читаемо». Форма `blocks` проходила round-trip, но `8456` превращался в список цифр — выявил Alex | | 7 | 🔴 **Артефакты — в проект, не в `/tmp`** | «качай доки в папку в проекте а не в темп» | | 8 | 🔴 **Бэкапы кода — только git. Не создавать копии «на всякий»** | «какой нахуй бэкап скриптов — там в гите все» (2026-09-17: «какой нах бэкап. у нас гит»). Коммит-чекпойнт ДО правки = бэкап; откат = `git checkout `. Копии файлов в проект **не плодить**. Исключение — **доки Obsidian перед УДАЛЕНИЕМ** (MCP-удаление необратимо) | @@ -812,6 +826,12 @@ git commit -m "Scenario YAML shape: bare action ids, no anchors" > Пункты 1, 2, 7 закрыты **аналитически** (семантика установлена и подтверждена на данных) **и** > реализованы: `set_var` (4 формы), `param`/`event` (49), `op`/`time`/`days_mask` (50), тела 59. > Все три круга чистые (§10.9). Код, потерянный при откате (§10.8), написан заново. +> +> 🆕 **Поздний вечер 2026-09-17 — круг 30 (§10.11):** п. 3 **версия подтверждена в новом снапшоте** +> (`21-13-18`): `#Z9986`/`#Z9990`/`#Z8601` — те же ссылки без объекта (было `8860`/`8864`/`8601` в `18-43-24`, +> номера переехали при перенумерации). Alex: «**завершить сценарий**». Ключ **не заведён** — имени в UI нет. +> Добавлен разбор `storeev`→`storeenv` и `puts`→`log` (был потерян вместе с надстройкой §10.8). +> Новый незакрытый остаток — **тела `Pickle`** (§10.11, `#Z9933=59,'3',0,0,0`, «Команда Pickle "3"»). ⚠️ **Остаётся `raw` / `unresolved`:** @@ -2012,8 +2032,117 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег > ``` > `type: var` (круг 29, вытеснявшая `set_var`) — **0 вхождений**. `set_var:` — 53. -> ✅ **`zont_config/config_local_2026-09-17_21-13-18.yml` ПЕРЕГЕНЕРИРОВАН** в целевой файл -> (не в `/tmp` — питфолл 34/5). Круг `18-43-24` → `661 → 661` ✅ (не затронут). +> 🆕 **`zont_config/config_local_2026-09-17_21-13-18.yml` ПЕРЕГЕНЕРИРОВАН на диск 2026-09-17 22:18** +> (83514 байт) — до этого правки гонялись только в `/tmp`, и Alex видел старый файл: питфолл, повторённый дважды. +> Круг с файла на диске: **774 → 774, потеряно 0, лишнее 0** ✅. + +--- + +## 10.11. 🔵 Круг 30 — `storeenv` / `log` / `flag` (2026-09-17, вечер) + +**Контекст.** Alex видел в YAML `descr: puts "Отладка"` + `args: [0,0,0]` и `descr: storeev I "…"` — тела +59, которые в прошлой сессии **уже были развёрнуты**, но потерялись вместе с незакоммиченной работой +(`git checkout b75c51f --` → питфолл 55). В коммите `07c076f` восстановили только `set_var`/`param`/`expr`. +Принятую форму подняли **из истории сессий** (Zulip/SQLite), а не изобрели заново. + +**ПРИНЯТАЯ форма (из истории — не выдумывать заново):** + +| Строка конфига | YAML | +|---|---| +| `#Z8598=59,'storeev I "инфо событие в пн, ср…"',0,0,0` | `storeenv: {level: info, text: 'инфо событие в пн, ср…'}` | +| `#Z9934=59,'storeev I "z"',0,0,0` | `storeenv: {level: info, text: z}` | +| `#Z9935=59,'storeev A "asdf"',0,0,0` | `storeenv: {level: alert, text: asdf}` | +| `#Z9933=59,'3',0,0,0` | `pickle: 3` — **команда Pickle**, литерал числом (Alex: «это Команда Pickle "3"», затем «pickle: 3 блядь») | +| `#Z9932=59,'puts "Отладка"',0,0,0` | `log: Отладка` | + +- 🔴 **в конфиге `storeev`, ключ YAML — `storeenv`** (подтверждено историей: «`storeev` или `storeenv`? → в конфиге `storeev`; ключ YAML — `storeenv`»). +- `level` — словами: `I`→`info`, `A`→`alert`, `W`→`warning`, `E`→`error` (обратный маппинг в энкодере). +- `log` — **плоский** ключ, без `args`. +- Отдельного типа «alarm» нет: сигналка идёт телом `storeev`. + +**Правки — ✅ ЗАКОММИЧЕНО в `375d01a` («Expand mini-script bodies: storeenv / log / pickle, trigger 50»):** + +| Файл | Что | +|---|---| +| `config-to-yml.py` | в ветке `t == 59` разбор тел `storeev ""` → `storeenv`, `puts ""` → `log`, голый литерал (`'3'`) → `pickle: <число>` | +| `config-to-yml.py` | хвост `set_var`-числа: `[0,1]` → `flag: 1` вместо `args: [0,1]` (значимые пишутся, нули нет; остаток — `args`) | +| `config-to-yml.py` | тип 50 в `dump_step` — раскрытие через `_set_var_target_body` (`days_mask`/`time_condition`) вместо голого `raw` | +| `config-to-yml.py` | `_is_body_inline` — `'target'` считается ссылкой, как `'id'`; убран мёртвый `sc.get('scenario') or sc` | +| `yml-to-config.py` | приём `storeenv`/`log`/`pickle` в **обеих** ветках (`emit_action` + `emit_step`) + в выводе орфанов; `_script_value_row` отдаёт `None` для `param`/`time_condition`/`days_mask`; `kind`/`flag` собираются в поля 3/4 | + +**ПИТФОЛЛ 57 🔴 — две ветки-близнеца в энкодере.** `emit_action` и `emit_step` — **разные** функции с +одинаковыми проверками. Правка, добавленная только в `emit_action`, дала +`unrecognised node {'id': 9932, 'log': 'Отладка'}` на **всех 8 конфигах** (exit ≠ 0). Формы тел надо +добавлять в **обе** ветки; проверять прогоном **всех** конфигов, не одного. + +**ПИТФОЛЛ 58 🔴 — «маленькая правка» `_is_body_inline` даёт потери.** Замена обхода на +`obj_id in v` (матч голых чисел в любом списке) выбила из вывода **6 объектов** на `21-13-18` +(`9146`, `9928`, `9930`, `9965`, `9985`, `9987` — все операнды/цели). Симптом: `774 → 768`. +Откат к строгому варианту (`'id'` + `'target'`, без матча чисел) вернул `774 → 774`. +🔴 Правка «по смыслу похоже» ломает сборку молча — **мерить счётчиком до и после**. + +**ПИТФОЛЛ 59 🔴 — конвертер писал в `/tmp`, Alex смотрел файл на диске.** Дважды за сессию: +«я до сих пор вижу `descr: puts "Отладка"`» — при том что прогон уже давал `log: Отладка`. +🔴 **Правки гонять ТОЛЬКО в ЦЕЛЕВОЙ файл** (`> zont_config/<имя>.yml`), проверку круга — по файлу +**с диска**, не по свежему прогону. Повтор питфолла 5 — он же записан в §7.1. + +**ПИТФОЛЛ 60 — `.get('scenario')` как «страховка».** В `_is_body_inline` стояло +`sc.get('scenario') or sc`, ключа `scenario` в словаре нет → выражение всегда вырождалось в `sc`. +Работало случайно. 🔴 Accessor к несуществующему ключу в горячем пути = мёртвый код, который +маскирует себя; писать прямо `sc`. + +**Итог круга 30 (все 8 конфигов, сравнение `sort` + `set`):** + +| Снимок | Строк | Потеряно | Лишнее | +|---|---|---|---| +| `12-12-28` | 623 → 623 | 0 | 0 | +| `14-16-35` | 685 → 685 | 0 | 0 | +| `16-02-18` · `16-13-28` · `17-45-00` · `18-43-24` · `19-53-21` | 686 → 686 | 0 | 0 | +| `21-13-18` | **774 → 774** | **0** | **0** | + +`descr: puts` в `21-13-18.yml` — **0 вхождений**; `descr: storeev` — 0. + +**Контроль по файлу на диске (после `375d01a`):** + +```yaml + - id: 9932 + log: Отладка + - id: 9933 + pickle: 3 + - id: 8598 + storeenv: + level: info + text: инфо событие в пн, ср, чт, пт, сб + - id: 9964 + set_var: {name: varname, value: 42, flag: 1} +``` + +Круг с **файла на диске**: `774 → 774`, потеряно 0, лишнее 0. + +**Осталось неизменным (не баги — нет формы от Alex):** + +1. **`Pickle`-тела — частично.** Литерал решён: `#Z9933=59,'3',0,0,0` → `pickle: 3` ✅. + Но `objcmd` **не раскрыт**: `#Z9925=59,'objcmd 8700 "1 %0"',14.5,0,1`, `#Z9929`, `#Z9931`, `#Z9948` + сейчас `descr` + `args`. 🔴 **`objcmd` из этой правки исключён намеренно** — я добавил только + ветку литерала, чтобы не выдумывать форму вызова. Alex дал форму **только для литерала** («pickle: 3»). + Нужна форма для вызова с форматом (`objcmd ""`) — тогда добавить. +2. **Тип 50 в `dump_step`** — `#Z8548=50,1,0,0,109` остаётся `raw: [50,1,0,0,109]`. Раскрытие + (`days: [mon,wed,fri,sat]`) написано в `_set_var_target_body` и в код внесено, но на `8548` + **не срабатывает** — путь `trigger:` идёт через другой вызов (§5.1), до `dump_step` объект 50 + не доходит. **Не откачено — не срабатывает.** Требует отдельной правки пути `trigger:`. +3. **`unresolved: true`** у `9986`, `9990`, `8601` — объектов нет в конфиге 21-13-18 (в `18-43-24` на их месте + были `8859`/`8861`; тела `puts "then-text"` / `storeev A "alert"` есть, а самих объектов нет). +4. **`scenario_orphans`: 27 → 21** — часть «орфанов» на самом деле разделяемые операнды, они раскрыты в + `args` шага, но строку в конце файла всё ещё получают. Полное удаление орфан-секции требует правки энкодера. + +> 🔴 **`unresolved: true` ≠ баг парсера.** Для ссылок на шаги без объекта (напр. «завершить сценарий», +> тип 11 — `9986`) своей строки в конфиге нет, поэтому тело писать нечего. Признак: id есть, `#Z=` — нет. + +### 10.11.1. Питфолл «`descr + args`» — законная форма, НЕ баг + +`descr` + `args: [поле2, поле3, поле4]` — это **raw-фоллбэк** для тел 59, которых нет в разборе +(`puts`, `storeev`, `objcmd`, литералы). `args: [0,0,0]` в нём — **буквально нули из строки конфига**, +а не потеря. Полный фоллбэк на `raw` **хуже**: круг перестаёт сходиться (`#Z9964` `42,0,1` → `42,0,0`, питфолл 54). --- @@ -2021,8 +2150,8 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег | Файл | Статус | |---|---| -| `config-to-yml.py` | ✅ форма §5 (`trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}`) **плюс** развёртывание тел (§10.9): `_script_value`, `_set_var_target_body`, `_target_set_name`, `PARAM_CODES`/`COND_EVENTS`/`_OP_NAMES`, разворот орфанов-59 · ✅ **закоммичено** (`07c076f`) | -| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 **плюс** сборка тел (§10.9): `_parse_script_raw`, `_script_value_row`, `_set_var_target_row`, `_set_var_body`, `_body_type`/`_collect_ids`, `_PARAM_TO_CODE`/`_EVENT_TO_CODE`/`_OP_TO_CODE`/`_SEC_TYPE` · ✅ **расхождение `#Z9964` ЗАКРЫТО** (полный `raw` 59 выводится как есть, питфолл 54) · ✅ **закоммичено** (`07c076f`) | +| `config-to-yml.py` | ✅ форма §5 (`trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}`) **плюс** развёртывание тел (§10.9): `_script_value`, `_set_var_target_body`, `_target_set_name`, `PARAM_CODES`/`COND_EVENTS`/`_OP_NAMES`, разворот орфанов-59 · ✅ закоммичено (`07c076f`) · 🆕 **круг 30 (§10.11):** разбор `storeev`→`storeenv`, `puts`→`log`, хвост `[0,1]`→`flag: 1`, `_is_body_inline` учитывает `'target'` · ⚠️ **не закоммичено** | +| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 **плюс** сборка тел (§10.9): `_parse_script_raw`, `_script_value_row`, `_set_var_target_row`, `_set_var_body`, `_body_type`/`_collect_ids`, `_PARAM_TO_CODE`/`_EVENT_TO_CODE`/`_OP_TO_CODE`/`_SEC_TYPE` · ✅ расхождение `#Z9964` ЗАКРЫТО (питфолл 54) · ✅ закоммичено (`07c076f`) · 🆕 **круг 30 (§10.11):** приём `storeenv`/`log` в **обеих** ветках (`emit_action` + `emit_step` — питфолл 57), `_script_value_row` отдаёт `None` для 49/50 · ⚠️ **не закоммичено** | | `test_roundtrip.py` | ✅ без изменений (в коммите `199f2b1`) · ✅ **оба круга зелёные после восстановления** | | `zont_config/config_local_2026-09-17_21-13-18.{txt,yml}` | 🆕 **актуальный рабочий снимок** (774 строки, 749 `#Z`) — «Простой тестовый сценарий» `#Z8456` содержит шаги `9925…9990`, `Values test` `#Z9324` не изменился. Круг ✅ `774 → 774`, различий **0** | | `zont_config/config_local_2026-09-17_18-43-24.{txt,yml}` | ↩️ **ВОССТАНОВЛЕН 2026-09-17** из `b75c51f` после ошибочной чистки. Форма `trigger:` + `action:`. Круг ✅ строк `686 → 686`, **потеряно 0, лишнее 0** (661 — это счёт `#Z`, 686 — все строки) | @@ -2030,12 +2159,15 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег | `zont_config/config_0FA7C33CC89F_…_12-12-28.txt` + 4 исторических `.txt` | ↩️ **ВОССТАНОВЛЕНЫ** из `b75c51f` (см. §10.8) | | `zont_config/archive/` | `-2`/`-3`/`-4` — закоммичены (`7ae0e32`) | -> 📌 **Состояние на 2026-09-17, конец сессии.** HEAD = `07c076f`, не запушен. -> Рабочая копия **закоммичена** — развёртывание тел (§10.9) в истории. +> 📌 **Состояние на 2026-09-17, конец сессии.** HEAD = **`375d01a`** («Expand mini-script bodies: +> storeenv / log / pickle, trigger 50»), не запушен. +> 🔴 **Изменённые файлы: `config-to-yml.py`, `yml-to-config.py` — незакоммиченного НЕТ.** > Развёртывание тел мини-скриптов и условий (`set_var`, `param`, `event`, `op`/`time`/`days_mask`, > `expr`/`objstate`/`const`) — **в коде, работает**, все три круга чистые, дефект `#Z9964` закрыт. > Круги: `21-13-18` → **774 → 774** · `18-43-24` → **686 → 686** · `19-53-21` → **686 → 686** > (потеряно 0, лишнее 0; сравнение по **содержимому**, `sort` + `comm`). +> ✅ **После `375d01a` проверены ВСЕ 8 конфигов в `zont_config/`:** `623→623`, `685→685`, `686→686` ×5, +> `774→774` — потеряно 0, лишнее 0. `descr: puts` в `21-13-18.yml` — 0 вхождений, `pickle: 3` на месте. > Расходится только **порядок строк** — унаследованное, не регрессия (§10.10). > ✅ **`19-53-21.{txt,yml}` закоммичены** (`07c076f`, круг `686 → 686`). В корне 13 @@ -2066,3 +2198,19 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег - [[family/how-to/home-automation]] §6 — ZONT в общем контуре, Modbus slave ID и регистры - [[family/how-to/ha-automations]] — автоматизации HA - [[family/how-to/gitea-config]] — Gitea: креды, создание репо, питфоллы + +--- + +## 13. Открытые вопросы к Alex (нужна форма, не догадки) + +> 🔴 Имя YAML-ключа и имена сущностей **не угадываются** (§5.7). Пока ответа нет — тела остаются +> в законном raw-фоллбэке `descr` + `args` и это **не баг**. + +| # | Вопрос | Где | Почему ждёт | +|---|---|---|---| +| 1 | ✅ **ЗАКРЫТ** — имя ключа для Pickle-литерала: `pickle: 3` (числом). Внесено в код | `9933` | — | +| 2 | форма для **Pickle-вызова**: `objcmd ""` — ключ `pickle: 'objcmd 8700 "1 %0"'` (строкой) или своё? | `9925`, `9929`, `9931`, `9948` | форма дана только для литерала | +| 3 | раскрывать ли **тип 50** прямо в шаге (`#Z8548` → `days: [mon,wed,fri,sat]`)? | `8548` | правка в коде есть, но путь `trigger:` её минует | +| 4 | «завершить сценарий» (тип 11 без объекта) — оставить `unresolved: true` или дать имя (`end`)? | `9986`, `9990`, `8601` | в конфиге объекта нет | +| 5 | удалять ли секцию `scenario_orphans` (21 запись — разделяемые операнды)? | низ YAML | требует правки энкодера, риск `#Z9964` | +| 6 | ✅ **ЗАКРЫТ** — круг 30 закоммичен (`375d01a`). Push — отдельной командой | оба конвертера | — |