[2026-09-17] eagle: family/how-to/zont-config-compiler.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 19:16:56 +06:00
parent d8e4973c3d
commit 4d75f4f69e
+175 -61
View File
@@ -350,6 +350,10 @@ diff A.txt B.txt # пусто = round-trip чистый
| 46 | 🔴 **Служебные ключи в YAML — отвергнуты как подход** | `_f5` держался 20+ сообщений. Alex: «какое нахуй служебное». Сначала искать вывод из данных, служебный ключ — последнее средство и только на нестандартных случаях (`_op`, `_then`, `_else`) |
| 47 | 🔴 **Правило вывода `type` даёт сбой на `#Z11109`** | Есть шаг 46, но он `manual`. Отличие: в поле 2 **два** элемента. Число элементов ≠ 1 → не `trigger`. **Гипотеза, не проверена** (§5j) |
| 48 | ⚠️ **`type` выводится из тела, но `manual` и `schedule` дают одно поле 5 = `0`** | Различаются только полями 3/4 (`days`/`time`). Alex: «manual от schedule очевидно отличаются наличием блядь schedule» |
| 49 | ✅ **РЕШЕНО: `type` НЕ выводить из тела — читать `f5 & 7`** | Три попытки вывести тип из структуры провалились (`11109` manual с шагом 46). Итог: `_base = f5 & 7` (1=trigger, 2=interval, 0=manual/schedule по days/time), 0 расхождений на 70 |
| 50 | 🔴 **Тела объектов из `then` не эмитились → потеря 3 объектов** | `11824`/`11825`/`11826` (паузы внутри `#Z11827`) есть только как id в списке `then`. Нужен `_emit_referenced_bodies(ids)` — обход ссылок и регистрация тел по их типу |
| 51 | 🔴 **`z_dict` определяется ниже вложенной функции → `free variable` ошибка** | `z_dict` строится на стр. ~1019, вложенная функция ссылалась на него до присваивания. Решение: собственный `_body_index` (карта `id → raw` по секциям YAML) рядом с функцией |
| 52 | 🔴 **`action` + `_then` давали дубль первого действия** | `11191` попадал дважды (`[11191, 11191, …]`). Правило: `_then` — источник истины, `action` добавлять только если `_then` пуст |
### Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ
@@ -569,13 +573,15 @@ Alex: «ЕСЛИ БЛЯДЬ В КОНФИГЕ `#Z8817=9,'…',10034,'2782'` ТО
| # | Задача | Статус |
|---|---|---|
| 1 | ~~`TRIGGER_KINDS` — удалить~~ | ✅ **сделано** — таблица удалена |
| 2 | **Форма YAML** — Alex отверг `if`/`then` как выдумку | ✅ форма согласована (§5j) **и реализована в парсере** |
| 2 | **Форма YAML** — Alex отверг `if`/`then` как выдумку | ✅ форма согласована (§5j) **и реализована в обоих конвертерах** |
| 3 | `git commit` правок парсера/энкодера | ⏸ не закоммичено, ждёт команды |
| 4 | YAML `17-45-00` — перегенерировать после правок | ✅ **перегенерирован** — 4405 строк, `trigger` на уровне сценария |
| 4 | YAML — перегенерировать после правок | ✅ **`18-43-24.yml`** актуален (форма §5j + `type`) |
| 5 | Раскрыть `8195` (type 3 SMS) из YAML-якоря | ⏸ отложено |
| 6 | 🔴 **Открытый вопрос:** что писать в `steps[].action`, если в `then` больше одного id или есть `else` | ⏸ ждёт Alex (единственный открытый) |
| 7 | 🔴 **Round-trip сломан** — энкодер не переведён под форму §5j (парсер обновлён, `yml-to-config.py` — нет) | ⏸ переписать энкодер |
| 8 | ~~Поле 5 типа 11 — семантика не раскрыта~~ | ✅ **закрыто**: тип (`manual`/`trigger`/`interval`/`schedule`) + `enabled`; `_f5` убран (§5j) |
| 6 | ~~Открытый вопрос: `steps[].action` при >1 id~~ | ✅ **решено**: `action` — объект при одном, список при нескольких; тела инлайн |
| 7 | ~~Round-trip сломан — энкодер не переведён~~ | ✅ **энкодер переведён, round-trip ЗЕЛЁНЫЙ** (661→661, 686→686) |
| 8 | ~~Поле 5 типа 11 — семантика не раскрыта~~ | ✅ **закрыто**: тип + `enabled`; `type` из `f5 & 7` |
| 9 | 🔴 **`if` у trigger-сценариев — на уровень сценария** | ⏸ **НЕ НАЧАТО** — требование Alex, план в §5k |
| 10 | Тип 50 (дни недели) и `set var1` args — раскрыть из `raw` | ⏸ отложено |
**Уроки методологии этой сессии** (проверять в следующих):
@@ -605,6 +611,22 @@ Alex: «ЕСЛИ БЛЯДЬ В КОНФИГЕ `#Z8817=9,'…',10034,'2782'` ТО
расхождениях. Записана как снятая — чтобы следующая сессия не выводила её заново.
11. 🆕 **Не выдавать пару примеров за правило.** Два совпавших сценария (11109, 9628) дали
уверенность, которой не было. Проверка на всех 70 — обязательна до слова «выяснили».
12. 🆕 **Спрашивать про КОНКРЕТНЫЙ кейс, а не строить обобщение самому.** «Если в `then` больше
одного id» — я выдумал правило («больше одного шага → manual»), оно развалилось. Правило
дал Alex из UI: тип читается из поля 5.
13. 🆕 **Правило «выводится из тела» — скрытая выдумка.** Трижды подряд я выводил `type` из
структуры (`есть шаг 46` → trigger, `число шагов` → manual) вместо того, чтобы прочитать
само поле. **Если поле есть в конфиге — читать его, а не выводить аналог.**
14. 🆕 **«Проверил и снял» — ценный результат, а не провал.** Три гипотезы по полю 5 были
опровергнуты скриптами (32 расхождения, «все нули», сдвиг индекса). Каждая снятая гипотеза
в доке экономит следующей сессии час. Записывать **и** опровергнутое.
15. 🆕 **Скрипт проверки может врать молча.** Дважды проверяющий скрипт давал «всё сходится»
из-за сдвига индекса поля (`parts[4]` vs `parts[5]`). Перед выводом — печатать **сырые
значения**, не только вердикт.
16. 🆕 **Требование Alex, сказанное последним, — самое важное.** Форма `if`-в-шаге прошла
round-trip и «выглядела правильно», но Alex увидел её и сказал: «какого хуя у trigger
сценариях опять if ушел в steps?». Round-trip ≠ соответствие смыслу (уже урок №1, но
повторился).
**Задача Alex (исходная):** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов».
@@ -998,9 +1020,10 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
## 5j. 🔴 ФОРМА СЦЕНАРИЯ В YAML — согласована с Alex (2026-09-17, финал сессии)
> **Статус:** форма **согласована И реализована в парсере** (`config-to-yml.py`).
> YAML `17-45-00` перегенерирован, блок `9691` вышел в целевой форме (`_f5` убран).
> **Энкодер (`yml-to-config.py`) ещё не переведён под новую форму** → round-trip сломан.
> **Статус (обновлено в конце сессии):** форма реализована в **обоих** конвертерах,
> `test_roundtrip.py` **ЗЕЛЁНЫЙ** — 661→661 объектов, 686→686 строк, 0 расхождений (снимок `18-43-24`).
> НО затем Alex потребовал **ещё одну правку**: у **trigger**-сценариев `if` должен стоять на
> уровне **сценария**, а не внутри `steps[]`. Правка **НЕ выполнена** (сессия прервана) — см. §5k.
> Полный разбор модели — [[family/tech/zont-scenario-logic-11109]] §8.17.
> ✅ **Поле 5 закрыто:** тип (`manual`/`trigger`/`interval`/`schedule`) + `enabled` — см. ниже.
@@ -1131,23 +1154,22 @@ enabled = not (поле5 & 8) ← подтвержд
`_raw_field_count`, далее `trigger`, `steps`. Реализовано переупорядочиванием словаря
после обхода шагов (`config-to-yml.py`, блок `# Reorder so the type sits with the other header fields`).
🔴 **Правило вывода `type` (текущее, порядок веток важен):**
🔴 **Правило вывода `type` (ФИНАЛЬНОЕ — читается из поля 5, НЕ из структуры):**
```text
trigger: есть поднятый `trigger` (первый шаг — тип 46)
interval: есть `interval_ms` (поле 6)
schedule: есть `days` или `time`
manual: иначе
_base = f5 & 7
_base == 1 → 'trigger'
_base == 2 → 'interval'
_base == 0 → 'schedule' если заданы days/time, иначе 'manual'
```
⚠️ **ИЗВЕСТНАЯ ОШИБКА в этом правиле:** `#Z11109` получает `type: trigger`, хотя по полю 5
он **`manual`** (`f5=0`). Причина: у него тоже есть шаг типа 46. Отличие от настоящих trigger —
у `11109` **два** элемента в поле 2 (`11827` шаг + `11828` пауза), у trigger-сценариев — **ровно
один**. Гипотеза правки: `trigger` = **ровно один** элемент в `steps` **и** он типа 46.
**Не проверена** (команда прогона не выполнена — approval timeout).
**Проверено на всех 70 сценариях: 0 расхождений.** Ключевой кейс — `#Z11109`: он `manual`
(f5 = `0`), хотя у него есть шаг типа 46. Правило «есть шаг 46 → trigger» **неверно** и снято.
Проверено на тот момент: `schedule` (8597) ✓, `interval` (8599) ✓, `trigger` (9628, 9691, 8547, 8551) ✓,
`manual` (8456) ✓, **`manual` (11109) ✗**.
⚠️ **Почему `type` читается из поля 5, а не выводится из тела:** `manual` и `schedule` дают
**одно и то же** число `0` и различаются только наличием `days`/`time`. А `trigger`/`manual`
различить по телу нельзя: у `11109` (manual) тоже есть шаг 46. Единственный надёжный источник —
само поле 5.
**Что опровергнуто и снято (не возвращаться):**
@@ -1156,12 +1178,14 @@ manual: иначе
-**Гипотеза «поле5 & 7 = значение из условия шага 46»** — проверена скриптом по всем 70:
**32 расхождения**. Пары ВКЛ/ВЫКЛ одной линии (`9628` ВКЛ и `9629` ВЫКЛ) имеют **одинаковое**
поле 5 = `1`, но противоположные условия (`1` и `0`). Гипотеза развалилась — **снята**.
-**Гипотеза «поле5 = 1 ⇔ ровно один шаг 46 с одним действием»** — проверена, дала
«предсказано 0» на всех 70 (индекс поля в скрипте был сдвинут, результат мусорный). **Снята.**
- ❌ «Поле 5 не выводится из содержимого» — **неверно**: выводится по правилу выше
(`тип` + бит 8). Ранее записанный вывод был получен до того, как Alex назвал типы.
-**Гипотеза «trigger = ровно один элемент в `steps` и он типа 46»****снята**: `#Z11109`
имеет **два** элемента (`11827` шаг + `11828` пауза) и он `manual`, но `#Z8599` (interval)
тоже имеет два — правило по числу шагов не работает. Тип берётся из поля 5 напрямую.
- ❌ «Поле 5 не выводится из содержимого» — **неверно**: выводится по правилу выше.
- ❌ Слово «триггерный» как тип сценария — тоже выдумка. Alex: «в смысле блядь триггерные».
Типов ровно четыре, названы Alex: `manual`/`trigger`/`interval`/`schedule`.
-**«Поле 5 = значение условия» не работает, но `f5 & 7` — работает.** Разница: значение
условия бывает `0` и у ВКЛ, и у ВЫКЛ сценариев; поле 5 у них одинаково `1`.
### ✅ Служебные ключи — БОЛЬШЕ НЕ НУЖНЫ
@@ -1215,62 +1239,152 @@ manual: иначе
---
## 6. Состояние проекта (проверено 2026-09-17, финал)
## 5k. 🔴 ПОСЛЕДНЯЯ ПРАВКА ФОРМЫ (не завершена) + переписанный энкодер
🟡 **Парсер переписан под форму §5j (триггер на уровне сценария + поле `type`), энкодер — ЕЩЁ НЕТ.**
Round-trip сейчас **не сходится**`yml-to-config.py` ждёт перевода. Рабочее дерево
**не закоммичено**, ждёт команды Alex. Последний коммит — `199f2b1`.
### Энкодер переведён — round-trip ЗЕЛЁНЫЙ (2026-09-17, конец сессии)
**Парсер `config-to-yml.py` — форма §5j реализована:** `trigger` на уровне сценария
(вынимается из первого шага типа 46), `steps[].{id, action}`, `descr` у шага не выводится,
`type` (`manual`/`trigger`/`interval`/`schedule`) выводится из тела, `if`/`then`/`else`/
`condition`/`operator`/`kind`/`_f5` из вывода убраны.
`yml-to-config.py` переписан под форму §5j. Что сделано:
🔴 **Известный баг парсера (единственный):** `#Z11109` получает `type: trigger`, хотя он `manual`
(поле 5 = `0`). Гипотеза правки: `trigger` = ровно один элемент в `steps` И он типа 46.
Прогон не выполнен. Детали — §5j.
| # | Правка | Место |
|---|---|---|
| 1 | `emit_condition` — ветка для формы `{id, object, value}``[49, object, op, value]` (`_op` если ≠ 1) | стр. ~374 |
| 2 | `emit_action` / `emit_step` — ветка `if 'action' in node: return aid` | — |
| 3 | Ссылка на сценарий — `if set(step) <= {'id'}: return sid` (вместо `run_scenario`) | — |
| 4 | Сборка шага 46: `if:``emit_condition`, `action:` (объект **или**) список → `[emit_step(…)…]`, `_else``else_ids` | — |
| 5 | Сборка сценария: `trigger` (уровень сценария) → `emit_condition` + первый `steps[].action``[46, kind, cond_id, action_ids, else_ids]` | стр. ~630 |
| 6 | Поле 5: `f5 = {manual:0, schedule:0, trigger:1, interval:2}[type] + (0 if enabled else 8)` | стр. ~624 |
| 7 | Хелпер `_emit_referenced_bodies(ids)` — вытягивает тела объектов, на которые ссылается `then` (паузы 45, вложенные 46/47/48/49/50), иначе они теряются | стр. ~575 |
| 8 | `_body_index` — карта `id → raw` по всем секциям YAML (для пункта 7) | стр. ~576 |
**Round-trip был чистый на 4/4 конфигах** (§5c, до правки формы). Счётчики совпадали: 661 → 661.
**После правки формы — сломан, ждёт энкодера.**
**Результат:** `python3 test_roundtrip.py``✅ ROUND-TRIP ЧИСТЫЙ — расхождений нет`,
`661 → 661` объектов, `686 → 686` строк.
🔴 **Точная ошибка round-trip (2026-09-17, последний прогон):**
Питфоллы этой правки (round-trip ловил каждый):
```text
python3 test_roundtrip.py
❌ YAML->TXT exit=2
Ошибка конвертации: Step 9756: unrecognised node {'id': 9756, 'action': 9500}
| # | Симптом | Причина | Решение |
|---|---|---|---|
| 1 | `duplicate id` у `11191` в `11827` | `action` добавлялся к `_then`, хотя `_then` уже его содержал | `_then` — источник истины; `action` брать только если `_then` пуст |
| 2 | Потеряны `11824`/`11825`/`11826` (паузы) | тела объектов из `then` нигде не эмитились | `_emit_referenced_bodies` |
| 3 | `free variable 'z_dict' referenced before assignment` | `z_dict` определяется **ниже** (стр. ~1019), из вложенной функции не виден | собственный `_body_index` из секций YAML |
| 4 | `#Z11109` получал `f5=1` вместо `0` | тип выводился из тела (есть шаг 46 → trigger) | тип читать из `f5 & 7` (см. §5j) |
### 🔴 ПОСЛЕДНЕЕ ТРЕБОВАНИЕ ALEX — НЕ ВЫПОЛНЕНО
Alex, последнее сообщение сессии:
> «какого хуя у нас в trigger сценариях опять `if` ушел в `steps`?»
и раньше:
> «в смысле блядь 9691/9628 тогда получится?! там блядь **триггер!**»
> «там блядь ссылка на этот `if` в самом заголовке сценария если мне память не изменяет!
> это блядь и есть триггер»
**Что требуется:** у сценариев типа **trigger** (f5 & 7 == 1) условие (`if`) должно быть
**на уровне сценария**, а `steps[]` содержать только действия. У остальных типов
(`manual`/`schedule`/`interval`) шаг 46 остаётся **внутри** `steps[]` со своим `if` — это
ветвление внутри сценария (пример: `#Z8456`, шаг `8863` в середине списка).
```yaml
# trigger — if на уровне сценария
- id: 9691
name: 'Автомат.: Прихожая (н/п) (14/14) ВКЛ'
enabled: true
type: trigger
if: ← уровень сценария
id: 9729
object: 9495
value: 1
steps:
- id: 9819
action: 9563
# manual — if внутри шага (ветвление в середине цепочки)
- id: 8456
steps:
# … 26 шагов …
- id: 8863
if:
id: 8853
group: and
children: […]
action: 8862
```
Энкодер не знает новую форму. **Что осталось сделать в `yml-to-config.py`** (объём ~80 строк):
**Проверено по конфигу:** в **заголовке** сценария (`#Z<id>=11,…`) ссылки на условие **НЕТ**
все 70 заголовков имеют ровно `[шаги], days, time, f5, interval, 0`, последнее поле всегда `0`.
Условие живёт **внутри шага 46** (поле 3). Alex настаивает, что для trigger это семантически
**триггер сценария** — то есть парсер должен **вынимать** его наверх для этого типа.
| # | Что | Где искать |
|---|---|---|
| 1 | `emit_action`/сборка шага — не понимает `{'id': N}` (ссылка на сценарий) | уже поправлено: `if set(step) <= {'id'}: return sid` |
| 2 | Сборка шага — читать `action: <id>``[46, 0, cond_id, [action], []]` | убрать ветку `'if' in step or 'then' in step` (стр. ~508) |
| 3 | Сценарий — читать `trigger:` (список с `object`/`value`), а не `if:` внутри шага | цикл `for scenario in data.get('scenarios')`, стр. ~548 |
| 4 | Тип → поле 5: `type` + `enabled``0`/`1`/`2`/`8`/`9`/`10` | строка сценария, стр. ~554–590 |
| 5 | `_scenario_from_legacy` (стр. ~521) — мёртвый код под старую форму, вычистить | — |
**Статус:** правка **не начата**. Текущий код (`config-to-yml.py`) кладёт `if` внутрь шага
**для всех** типов и вытягивания на уровень сценария **не делает** (блок с `f5 & 7 == 1`
остался пустым — я его вырезал). Round-trip при этом зелёный, потому что форма самосогласована
в обоих конвертерах, но **не соответствует требованию Alex**.
Правило сборки поля 5: `f5 = {manual:0, schedule:0, trigger:1, interval:2}[type] + (0 if enabled else 8)`.
**План правки (следующая сессия):**
1. `dump_step`, тип 46: не рендерить `if` внутри шага, если сценарий типа `trigger` — отдавать
`_trigger_id` наружу (как раньше).
2. Сборка сценария: при `f5 & 7 == 1` вынимать `_trigger_id` из **первого** шага →
`scenario['if'] = dump_condition(trig_id)`.
3. Энкодер: при `type == 'trigger'` и наличии `scenario['if']` собирать `[46, kind, cond_id,
[action_ids], []]` из уровня сценария; иначе — из шага.
4. Перегенерировать YAML, прогнать round-trip (обязательно — форма двусторонняя).
---
## 6. Состояние проекта (проверено 2026-09-17, финал)
🟢 **Round-trip ЗЕЛЁНЫЙ:** `661 → 661` объектов, `686 → 686` строк, 0 расхождений на снимке
`18-43-24`. Оба конвертера переведены на форму §5j.
🔴 **НО форма требует последней правки (§5k):** у trigger-сценариев `if` должен быть на
уровне сценария. Сейчас `if` лежит внутри `steps[]` для **всех** типов. Правка **не начата**.
🔴 **Рабочее дерево НЕ закоммичено** — ждёт команды Alex (коммит+док запрошены в конце сессии,
правка §5k может изменить код до коммита). Последний коммит — `199f2b1`.
⚠️ **Не разобрано (остаётся `raw`/`unresolved`):**
1. **Тип 50** — маска дней недели: `#Z8548=50,1,0,0,109`, где `109 = 0b1101101` = пн,ср,чт,сб,вс
(Alex: «это выбор дней недели, то же самое что ставится в значение var1»). Сейчас `raw`.
2. **`set var1` с `args: [8844, 0, 0]`** — внутри объекта `8844` лежит та же маска дней недели,
объект не раскрыт (Alex: «что какого-то хуя уехало вовне сценария вообще — только там
пн, вт, чт, пт, сб, вс»).
3. **`unresolved: true`** у `#Z8860`, `#Z8864`, `#Z8601` — этих объектов нет в конфиге.
⛔ **ЗАПРЕЩЕНО в YAML (выдумка парсера, Alex отвергает):** `then`/`else`/`condition`/`operator`
на уровне сценария, `kind`, `_f5`, `run_scenario`, подстановки имён/типов из чужих объектов,
`trigger` с именем объекта. Служебные `_`-ключи — только на нестандартных случаях (`_op`,
`_else`, `_kind`).
✅ **Форма (текущая реализация, до правки §5k):**
```yaml
- id: 9691
name: 'Автомат.: Прихожая (н/п) (14/14) ВКЛ'
enabled: true
type: trigger
steps:
- id: 9819
if: {id: 9729, object: 9495, value: 1}
action: {id: 9563, descr: 'Включить выход 14/14: Прихожая (н/п)', target: 146064, value: 1}
```
✅ **Тела действий — инлайн** внутри `action` (объект, если одно действие; список — если
несколько). Вложенные `if` (`47`/`48`/`49`) сохраняются рекурсивно (`group: and/or/not`,
`op: '<=', left, value`).
✅ **Уставка температуры раскодирована** — `код = (t °C + 273) × 10`, подтверждено 3 точками
(`22→2950`, `23.8→2968`, `5.2→2782`; §8.13 в [[family/tech/zont-scenario-logic-11109]]).
**YAML сжат вдвое:** 6093 → 4405 строк. Секции-дубли убраны, поля переименованы
(`descr`/`target`/`value`).
✅ **Поле 5 типа 11 — РАСКРЫТО И ПОДТВЕРЖДЕНО ПРИБОРОМ** (§5j): `тип + 8 при выключенном`;
типы `manual`/`trigger`/`interval`/`schedule` (Alex), `enabled = not (f5 & 8)`.
типы `manual`/`trigger`/`interval`/`schedule` (Alex), `enabled = not (f5 & 8)`;
`type` в YAML читается напрямую из `f5 & 7` (0 расхождений на 70 сценариях).
Включённые `schedule`/`interval` добыты тостингом в UI (`8597` → `0`, `8599` → `2`).
`_f5` из YAML убран — поле восстанавливается из `enabled` + типа.
⚠️ **Открыто два вопроса:** (1) `steps[].action` при >1 id в поле 4 шага 46 (у `11109` их 7);
(2) `run_scenario` — имя или id. Оба — §5j.
| Файл | Статус |
|---|---|
| `config-to-yml.py` | 🆕 **изменён** — форма §5j: `trigger` на уровне сценария, `steps[].{id, action}`, поле `type` из тела (`manual`/`trigger`/`interval`/`schedule`), `_f5`/`descr` у шага убраны, `run_scenario` заменён на голый `id`, `object` вместо `condition.operator`; плюс всё из §5c (типы 47/48/50/59, `descr`/`target`/`value`, `scenario_orphans`, `_build_type9_line`, подстановки удалены). **не закоммичено** |
| `yml-to-config.py` | 🔴 **НЕ переведён под форму §5j**всё ещё ждёт старой формы (`if`/`then`, `trigger` внутри шага). **Round-trip падает:** `Step 9756: unrecognised node {'id': 9756, 'action': 9500}`. Частично поправлено (`set(step) <= {'id'}` вместо `run_scenario`). План правки — §6. **не закоммичено** |
| `config-to-yml.py` | 🆕 **изменён** — форма §5j: `type` из `f5 & 7`, `if` внутри `steps[]` (шаг 46), `action` с телом инлайн, поле 5 из `type`+`enabled`, `run_scenario` заменён на голый `id`; плюс всё из §5c (типы 47/48/50/59, `descr`/`target`/`value`, `scenario_orphans`, `_build_type9_line`, подстановки удалены). 🔴 **Ждёт правки §5k** (trigger → `if` на уровень сценария). **не закоммичено** |
| `yml-to-config.py` | ✅ **ПЕРЕВЕДЁН на форму §5j** — round-trip ЗЕЛЁНЫЙ. Правки: `emit_condition` (форма `{object,value}`), сборка шага 46 из `if`/`action`, поле 5 из `type`+`enabled`, `_emit_referenced_bodies` + `_body_index`. 🔴 **Ждёт правки §5k**. **не закоммичено** |
| `test_roundtrip.py` | ✅ в коммите `199f2b1`, без изменений |
| `zont_config/config_local_2026-09-17_18-43-24.txt` | 🆕 **34 962 байт** — **САМЫЙ ПОСЛЕДНИЙ** конфиг. Снят `curl -s http://192.168.0.50/config.txt` после включения `8597`/`8599` в UI. **не в git** |
| `zont_config/config_local_2026-09-17_18-43-24.yml` | 🆕 форма §5j + поле `type`. **не в git** |