From ab32dd434cec319a34365ed756f5bf55fe91d16b Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 17 Sep 2026 23:10:24 +0600 Subject: [PATCH] =?UTF-8?q?ZONT=20=D0=B4=D0=BE=D0=BA:=20=D0=BA=D1=80=D1=83?= =?UTF-8?q?=D0=B3=D0=B8=2037-40=20(end,=20=D0=BE=D1=80=D1=84=D0=B0=D0=BD?= =?UTF-8?q?=D1=8B,=20trigger.step=5Fid,=20set=5Fvar=20args)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- personal/projects/zont-config-compiler.md | 212 ++++++++++++++++------ 1 file changed, 157 insertions(+), 55 deletions(-) diff --git a/personal/projects/zont-config-compiler.md b/personal/projects/zont-config-compiler.md index 201cfcc7..a43bc28c 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-17k38' +updated: '2026-09-17k40' tags: - personal - zont @@ -34,6 +34,13 @@ aliases: - ZONT терминатор сценария - ZONT unresolved = конец сценария - ZONT from отменён + - ZONT trigger step_id + - ZONT action отменён + - ZONT шаг 46 id шага + - ZONT поле 5 записи 59 + - ZONT args выдуманный ключ + - ZONT круг 39 + - ZONT круг 40 related: - '[[family/tech/zont-api]]' - '[[family/how-to/home-automation]]' @@ -206,7 +213,7 @@ python3 test_roundtrip.py zont_config/config_X.txt | 52 | Modbus-регистры | `name`, `register`, `bit_width`, `repeat_period`, `num_vars` | вложены в устройство 51 | | 53 | Аналоговые выходы | `name`, `min`, `max`, `value`, `offset`, `scale`, `flags`, `address` | `analog_outputs` | | 57 | MQTT-топики | `topic`, `sensors` | `mqtt_topics` | -| **59** | Мини-скрипт ZONT | `descr`, `args[]` (+ `target`/`value` для `objcmd *`); тела `set var`/`puts`/`storeev` — §5.10 | инлайн в `then`/`else` | +| **59** | Мини-скрипт ZONT | `descr`, `args[]` (+ `target`/`value` для `objcmd *`); тела `set var`/`puts`/`storeev` — §5.10. **Поле 5 = признак литерала** (`1` если поле 2 число), в YAML не пишется — §21.5 | инлайн в `then`/`else`/`steps` | > 🔴 Для каждого типа указаны только **реально декодируемые** поля; остальное — `raw` и пишется как есть. @@ -253,41 +260,45 @@ python3 test_roundtrip.py zont_config/config_X.txt ### 5.1. Trigger-сценарий -`steps` состоит **ровно из одного шага типа 46** — условие **переносится** в `trigger:` наверх, -у шага остаётся `action` — **голый id** действия (тело живёт в своей секции): +`steps` состоит **ровно из одного шага типа 46** — условие **переносится** в `trigger:` наверх +вместе с **id шага** (`step_id`), у самого шага в `steps` ничего не остаётся: там только **тела +действий** (их id + развёрнутое тело): ```yaml -- id: 9688 - name: 'Автомат.: (н/п) (14/12) ВЫКЛ' - enabled: true +- id: 8547 + name: 'Тестовый сценарий по времени' + enabled: false trigger: - id: 9726 - object: 9494 - value: 0 + id: 8548 + type: days_mask + days: [mon, wed, thu, sat, sun] + step_id: 8550 steps: - - id: 9816 - action: 9560 + - id: 8549 + log: test ``` Соответствие конфигу: ```text -#Z9688=11,'Автомат.: (н/п) (14/12) ВЫКЛ',[9816],0,0,1,0,0 -#Z9816=46,0,9726,[9560],[] ← шаг: условие 9726, действие 9560 -#Z9726=49,9494,1,0 ← триггер: объект 9494, значение 0 -#Z9560=5,'Выключить выход 14/12: (н/п)',146032,1,0,0,[],0,0,0,256 +#Z8547=11,'…',[8550],0,0,9,0,0 ← сценарий; поле 3 = [8550] (id шага 46) +#Z8550=46,0,8548,[8549],[] ← шаг: условие 8548, действие 8549 +#Z8548=50,1,0,0,109 ← условие → trigger: (type: days_mask) +#Z8549=59,'puts "test"',0,0,0 ← тело действия → steps[] ``` | YAML | Строка конфига | Поля | |---|---|---| -| `id`, `name` | `#Z9688=11,…` | 1, 2 | -| `enabled` | `#Z9688` поле 5 | `not (f5 & 8)` — **производное** | -| `trigger.id` | `#Z9726=49,…` | 1 (id условия) | -| `trigger.object` | `#Z9726` | 2 (объект под наблюдением) | -| `trigger.value` | `#Z9726` | 4 (значение; поле 3 = оператор) | -| `steps[].id` | `#Z9816=46,…` | 1 (id шага) | -| `steps[].action` | `#Z9816` | 4 (список действий шага) | -| тело действия | `#Z9560=5,…` | своя строка, в свою секцию | +| `id`, `name` | `#Z8547=11,…` | 1, 2 | +| `enabled` | `#Z8547` поле 5 | `not (f5 & 8)` — **производное** | +| `trigger.id` | `#Z8548=50,…` | id условия | +| `trigger.type`/`days`/`op`/`time` | `#Z8548` | разбор по полю 2 | +| `trigger.step_id` | `#Z8550=46,…` | id шага 46 (поле 3 сценария `[8550]`) | +| `steps[].id` | `#Z8549=59,…` | id действия (поле 4 шага `[8549]`) | +| тело действия | `#Z8549=59,…` | своя строка, разбор по типу | + +> 🔴 **`step_id` — служебный ключ-ссылка, а не выдуманное поле.** Он нужен, чтобы собрать шаг 46 +> обратно; энкодер вынимает его из `trigger` и подставляет в поле 3 строки 46. См. §21.3. ### 5.2. Manual / if-конструкция @@ -322,7 +333,11 @@ python3 test_roundtrip.py zont_config/config_X.txt | У шага есть свой `if`? | Ключ | Почему | |---|---|---| | **да** — условие внутри шага | **`then`** (+ `else` парой) | условие и ветки — единая конструкция if/then/else | -| **нет** — условие поднято в `trigger:` | **`action`** | от шага остался только список действий | +| **нет** — условие поднято в `trigger:` | шага в `steps` **нет вообще** | id шага уезжает в `trigger.step_id`, а `steps` = **тела действий** (§21.3) | + +> 🔴 Ключ **`action` ОТМЕНЁН** (круг 39, `8f2edd4`). Был компромиссом: шаг оставался в `steps`, а +> действия писались голыми id, из-за чего орфан `- id: 8550 / action: 8549` читался как обрывок. +> Alex: «какой нахуй action» · «steps блядь - log» · «засунь в триггер». > 🔴 Это **не противоречие** в требованиях Alex: он говорил про **разные шаги**. «какого хуя там > then блядь?!» — про шаг без `if`; «ДА БЛЯДЬ! Then конечно!!!» — про шаг с `if`. @@ -416,13 +431,19 @@ enabled = not (f5 & 8) | ⛔ **`type: var` / `object: <голый id>` для тела `set var`** | Дублирующая ветка `t == 'var'` вытеснила `set_var` и оставила голый id цели. Alex: **«где блядь `set_var`»**. Форма удалена, `set_var` — единственная; цель-скрипт разворачивается на месте через `target` + `type` (§10.7, круг 29) | | ⛔ **`tail: [0,0]` у тела `set var`** | выдуманный ключ «на всякий случай»; Alex: **«это че за ебанина?»** → хвост идёт тем же `args`, что у прочих шагов (§15.2) | | ⛔ **`flag: 1` / `kind` внутри `set_var`** | имя поля 4 я выдумал; Alex: **«ФЛАГ БЛЯДЬ!!!!!!»**. Реальный `flag` — только поле 1 записи 46 (§15.1) | +| ⛔ **`args: [0, 1]` у `set_var`** | **выдумка ото начала**: `0` — поле 4 (всегда ноль), `1` — поле 5 (**производное**: «в поле 2 литерал»). Alex: «да блядь! опять?!» (§21.2.1, `4b99d8e`) | +| ⛔ **`action: ` / шаг 46 в `steps`** | **ОТМЕНЕНО** (круг 39): шаг не пишется в `steps`, его id едет в `trigger.step_id`, `steps` = тела действий. Alex: «какой нахуй action» (§21.3) | | ⛔ **`from: 0` / `value: 0` — поле 2 тела `set var`** | Alex: **«Я СКАЗАЛ СНЕСТИ, А НЕ МЕНЯТЬ НАЗВАНИЯ!»**. Поле 2 **не раскрывается** в YAML; энкодер ставит `0` сам (§17) | | ⛔ **`raw: [50,1,0,0,109]` в `trigger:`** | расписание — тот же объект, что цель `set_var`; раскрывать `type: days_mask` + `days` (§19) | | ⛔ **`raw: [59, 'puts "…"', 0,0,0]` в орфанах** | `puts`/`storeev` — разобранные тела; орфан раскрывается как `log:` / `storeenv:` (§19.3) | +| ⛔ **`action: ` и вообще ключ `action`** | **ОТМЕНЁН** (круг 39, `8f2edd4`). При поднятом условии шага в `steps` нет вообще: id шага едет в `trigger.step_id`, `steps` = тела действий. См. §5.3, §21.3 | +| ⛔ **`args: [0, 1]` у `set_var` с числовым полем 2** | поле 5 записи 59 — **производное** («поле 2 = литерал, а не id»), энкодер ставит сам. Alex: «да блядь! опять?!» (§21.5) | +| ⛔ **раскрывать поля 4/5 записи 59 в YAML** | у `expr` поле 4 = `2` и у `set` поле 5 = `1` — **одно и то же производное поле** («операнд/значение — литерал»). В YAML не пишется, восстановимо по составу (§21.5) | -✅ **РАЗРЕШЕНО:** `trigger:` (выводится из тела — один шаг 46), `if`/`then`/`else`/`flag` — **реальные поля -записи 46**, `action` — имя списка у шага с поднятым условием, `set_var: {id, name, object/operator/value -| days/days_mask}` — **разобранное значение** (§10.1), `log: <текст>` — плоский (§10.3), +✅ **РАЗРЕШЕНО:** `trigger:` (выводится из тела — один шаг 46) с ключом **`step_id`** (id шага 46) и +**телами действий прямо в `steps`**, `if`/`then`/`else`/`flag` — **реальные поля записи 46**, +`set_var: {id, name, object/operator/value|days/days_mask}` — **разобранное значение** (§10.1), +`log: <текст>` — плоский (§10.3), `storeenv: {level, text}` — **разобранный вызов** журнала событий (§10.2), **`op: '…'` + `time: 'HH:MM'`** у `type: time_condition` — знак подтверждён на 4/4 условиях (§10.5), **`param: <имя>`** у `type: condition` при поле 3 = `0` — словарь по типу владельца (§10.6). @@ -467,9 +488,11 @@ enabled = not (f5 & 8) | 29 | `param` заведён **словами** по типу владельца | «словами» · «как везде» | ✅ **принято** — `param: pressure`, как `event: upper_threshold` (§10.6) | > 📌 Круги 10–11 разобраны в §10.2, 12–18 — в §10.1, 14 — в §10.3, 19–24 — в §10.4/§10.5. -> Итог: `storeenv: {level: info|alert, text}` — два ключа, `id` команды снаружи, словами, без `event`; -> `set_var: {id, name, type, object/event|value}` — плоско; `log: <текст>` — плоская строка; -> объект 50 — две формы (`time_condition` / `days_mask`). +Итог: `storeenv: {level: info|alert, text}` — два ключа, `id` команды снаружи, словами, без `event`; +`set_var: {id, name, type, object/event|value}` — плоско; `log: <текст>` — плоская строка; +объект 50 — две формы (`time_condition` / `days_mask`). ⛔ **`args` у `set_var` — выдумка** (поле 5 +производное, §21.2.1). ⛔ **`action` отменён** — шаг 46 не пишется в `steps`, его id едет в +`trigger.step_id` (§21.3). > 🔴 **Три системных корня кругов 12–18:** > **(а) ключ-контейнер на один смысл** — `{var1: …}`, `{text: …}`: обёртка ради обёртки, Alex видит @@ -2972,19 +2995,19 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и (а) «ключ переименовать в `end`» и (б) «он и означал конец, поэтому голый id верен, откатывать нечего». Развести **одним вопросом** («какое слово ставить?»), не выбирать самому — это и есть питфолл 77. -### 20.3. ⏳ Открыто на конец сессии +### 20.3. ⏳ Открыто (обновлено в конце сессии 2026-09-17) | # | Что | Кто должен решить | |---|---|---| | 1 | ✅ **ЗАКРЫТО** — смысл `unresolved` = конец сценария, ключ переименован в **`end`** (§21.1) | — | -| 2 | 🔴 **`steps` триггер-сценария**: убрать `- id: 8550 / action: 8549`, тело действия — прямо в `steps`. **Куда девается id шага `8550`?** Три попытки откачены (§21.3) | **Alex** — назвать форму | -| 3 | ✅ **ЗАКРЫТО формально** — `objcmd` разобран: `descr` + `args` + `target` (`10029`/`10033`/`10036`/`10053`) | — | -| 4 | Имя хвоста `args: [0, 1]` у `#Z10069` (§15.2) — открыто с круга 32 | **Alex** | -| 5 | Поле 4 = `2` у `expr` (`10077`–`10085`) — имя не дано | форма не согласована | -| 6 | Push в Gitea — 11 коммитов не запушены | по команде Alex | +| 2 | ✅ **ЗАКРЫТО** — `action` выброшен, шаг 46 не пишется в `steps`, его id едет в `trigger.step_id`, `steps` = тела действий (§21.3, `8f2edd4`) | — | +| 3 | ✅ **ЗАКРЫТО** — `objcmd` разобран: `descr` + `args` + `target` (`10029`/`10033`/`10036`/`10053`) | — | +| 4 | ✅ **ЗАКРЫТО** — `args: [0, 1]` у `#Z10069` была выдумкой; поле 5 записи 59 производное, энкодер ставит сам (§21.5, `4b99d8e`) | — | +| 5 | Поле 4 = `2` у `expr` (`10077`–`10085`) — то же производное поле, что поле 5 (§21.5). Имени не дано, энкодер восстанавливает по признаку «второй операнд — литерал» | форма не согласована | +| 6 | Push в Gitea — **15** коммитов не запушены | по команде Alex | -> 🔴 **Приёмка не выполнена по п.2** — код не трогать, пока форма не названа. Всё остальное в этом доке — -> ✅ закоммичено и проверено на 9 конфигах. +> ✅ Единственный открытый пункт — 5 (поле 4 у `expr`), и он **не блокирует** круг: 9/9 зелёный, +> все открытые ранее вопросы закрыты. Всё в этом доке проверено на 9 конфигах. --- @@ -3048,30 +3071,65 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и | | | |---|---| -| **HEAD** | **`272f4c6`** — «trigger: id шага 46 переехал в trigger.step, steps = тела действий» | -| **Содержимое `ba6ef44`** (история) | только снятие флага: `config-to-yml.py` (4 точки) + регенерация артефакта. 2 файла, `+6/-7` | +| **HEAD** | **`4b99d8e`** — «set_var: args убран у числового поля 2, поле 5 ставится само» · ✅ **ЗАПУШЕН** | | **Рабочее дерево** | чистое по конвертерам | -| **Файл на диске** | `config_local_2026-09-17_22-26-26.yml` — 757 объектов; `4171`/`4178`/`4233` = `end: true` | +| **Файл на диске** | `config_local_2026-09-17_22-26-26.yml` — 757 объектов; `trigger.step_id`, `end: true` у терминаторов, `args` только у `objcmd` | | **Круг** | ✅ **9/9** — потеряно 0, лишнее 0 | -| **Коммиты сессии** | `4c6a6f5` · `806ed2e` · `b770bed` · `ba6ef44` · `0cbbeb8` · `2007771` · `0c4b9a6` · `821659b` · `16ec710` · `375d01a` · `07c076f` — **push не делался** | +| **Коммиты сессии** | `4b99d8e` · `8f2edd4` · `272f4c6` · `6d0b6a5` · `4c6a6f5` · `806ed2e` · `b770bed` · `ba6ef44` · `0cbbeb8` · `2007771` · `0c4b9a6` · `821659b` · `16ec710` · `375d01a` · `07c076f` — ✅ **ЗАПУШЕНЫ** (`d94b809..4b99d8e`) | | **Незакоммичено в `zont_config/`** | 2 удалённых `.yml`-снапшота (`18-43-24`, `19-53-21`), новый `.txt` | -| **Открыто** | §20.3 — п.1 ✅ закрыт (`end`), п.3 `objcmd` ✅ форма есть (`descr`+`args`+`target`); остались `args`-хвост `#Z10069`, поле 4 `expr`, push | +| **Открыто** | только поле 4 = `2` у `expr` (`10077`–`10085`) — имя не дано; всё остальное ✅ закрыто | -### 21.3 ✅ ЗАКРЫТО — Круг 39 (ФИНАЛ): `action` выброшен, id шага едет в `trigger.step` +### 21.2.1 ✅ Круг 40 (ФИНАЛ) — `set_var`: `args` выдуман, поле 5 производное -**«какой нахуй action»** · **«steps блядь - log»** · **«засунь в триггер»** — целевую форму Alex -назвал. Итог: ключа `action` нет; шаг 46 не пишется в `steps`; его id поднимается в `trigger:` -ключом **`step`**; `steps` содержит **тела действий**. +**«да блядь! опять?!»** на `args: [0, 1]` у `#Z10069` — Alex отверг его в третий раз (круги 32, +36, 40). Причина найдена в данных: + +``` +#Z10069=59,'set varname',42,0,1 ← поле 5 = 1 +#Z10029=59,'objcmd 8700 "1 %0"',14.5,0,1 +#Z10077=59,'expr "%0 + %1"',9146,1,2 ← поле 5 = 2 +``` + +| Поле 5 | Сколько | Что значит | +|---|---|---| +| `0` | **80** записей | поле 4 (поле 2) — **id объекта** либо пусто | +| `1` | **2** (`10029`, `10069`) | в поле 2 — **литерал значения** (`14.5`, `42`) | +| `2` | **5** (`10077`–`10085`) | второй операнд `expr` — **литерал** | + +**Все 7 записей с полем 5 ≠ 0 — ровно те, где поле 4 не является id.** Значит поле 5 — +**производное**, в YAML не пишется; энкодер ставит его сам по признаку «в поле 2 число». + +| Точка | Что | +|---|---| +| `config-to-yml.py` (`~938`) | ветка «поле 2 = число»: `{'name', 'value'}` — **без `args`** | +| `yml-to-config.py` (`~688`) | `[59, 'set %s', val, 0, 1]` — поле 5 = `1` ставится само, поле 4 всегда `0` | + +Круг ✅ **9/9**, `#Z10069` → `set_var: {name: varname, value: 42}`. + +**Коммит:** **`4b99d8e`** («set_var: args убран у числового поля 2, поле 5 ставится само»). +**Push выполнен:** `d94b809..4b99d8e` (14 коммитов), `origin/main` = HEAD. + +🔴 **ПИТФОЛЛ 86 — `args` был выдумкой от начала и до конца.** Ключ рождался трижды и трижды +отвергался; правильный ответ лежал в **данных**: поле 5 — не «хвост», а признак литерала, и +восстанавливается энкодером. Прежде чем давать имя «остатку полей», проверить, не является ли +этот остаток **производным от уже разобранных полей** (ср. питфолл 82). + +### 21.3 ✅ ЗАКРЫТО — Круг 39 (ФИНАЛ): `action` выброшен, id шага едет в `trigger.step_id` + +**«какой нахуй action»** · **«steps блядь - log»** · **«засунь в триггер»** · **«только пусть +`id: 8548` / `step_id: 8550`»** — целевую форму Alex назвал. Итог: ключа `action` нет; шаг 46 не +пишется в `steps`; его id поднимается в `trigger:` ключом **`step_id`**; `steps` содержит +**тела действий**. ```yaml - id: 8547 name: тестовый сценарий по времени enabled: false trigger: - step: 8550 # id шага 46 — раньше был шаг в steps с action: 8549 - id: 8548 # условие шага + id: 8548 # условие шага — ПЕРВЫМ ключом type: days_mask days: [mon, wed, thu, sat, sun] + step_id: 8550 # id шага 46 — следом steps: - id: 8549 # тело действия, развёрнуто на месте log: test @@ -3079,13 +3137,13 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и | Точка | Что | |---|---| -| `config-to-yml.py` (`~1161`) | `scenario['trigger'] = {'step': , **<условие>}`; `steps` = `dump_step` по `then` | -| `config-to-yml.py` `_is_body_inline` (`~1240`) | `v.get('step') == obj_id` считается ссылкой — иначе шаг падал в орфаны (**было 70**) | -| `yml-to-config.py` (`~1071`) | из `trigger` вынимается `step` + `flag`, шаг `46` собирается обратно, `steps` → его `then` | +| `config-to-yml.py` (`~1161`) | `scenario['trigger'] = {**<условие>, 'step_id': }` — условие первым; `steps` = `dump_step` по `then` | +| `config-to-yml.py` `_is_body_inline` (`~1240`) | `v.get('step_id') == obj_id` считается ссылкой — иначе шаг падал в орфаны (**было 70**) | +| `yml-to-config.py` (`~1071`) | из `trigger` вынимается `step_id` + `flag`, шаг `46` собирается обратно, `steps` → его `then` | Круг ✅ **9/9 чистый**, орфанов **4** (было 6: `8549`/`8554` развернулись). -**Коммит:** **`272f4c6`** («trigger: id шага 46 переехал в trigger.step, steps = тела действий»). +**Коммиты:** `272f4c6` (ключ `step`, id шага первым) → **`8f2edd4`** (`step_id`, условие первым). #### Откаченные попытки (не возвращаться) @@ -3095,6 +3153,7 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и | 2 | декодер: тело **плоско** в `action` | ключ `action` остался | ⛔ | | 3 | декодер: `steps` = тела, id шага ключом `step:` — **без** правки `_is_body_inline` | **65–70 орфанов**: шаги 46 уехали в `raw` | ⛔ | | 4 | `then:` на шаге вместо `action` | не то: `then` закреплён за шагом со своим `if` | ⛔ | +| 5 | ключ `step:` (без `_id`) | имя отвергнуто: «только пусть `step_id`» | ⛔ | 🔴 **ПИТФОЛЛ 83 — четыре круга на одну форму.** Я правил по одному месту и гонял круг. Правильно: выяснить форму **одним вопросом** («куда девается id шага 8550?» — ответ Alex: «засунь в триггер»), @@ -3109,9 +3168,52 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и `scenario_orphans` с `raw`. Поднимая id шага в `trigger:`, **сразу** добавить ключ в `_is_body_inline`. -### 21.4. ✅ Круг 38 (ФИНАЛ) — `action` развёрнут, шаги ушли из `scenario_orphans`** +### 21.5. ✅ Круг 40 — поле 5 записи 59 = производное, `args` у `set_var` убран + +**Alex: «да блядь! опять?!»** на `set_var: {name: varname, value: 42, args: [0, 1]}`. + +**Строка конфига:** `#Z10069=59,'set varname',42,0,1` — поле 2 = `42` (число), поле 4 = `0`, поле 5 = `1`. +Хвост `(0, 1)` был склеен в выдуманный `args`. + +**Проверено по всем 87 записям типа 59 в дампе:** + +| Поле 5 | Кол-во | Кто | Смысл | +|---|---|---|---| +| `0` | **80** | все `set <имя>` со ссылкой, `puts`, `storeev`, `objstate`, `expr` с двумя id | поле 4 — **id объекта** либо пусто | +| `1` | **2** | `10029` (`objcmd`, поле 2 = `14.5`), `10069` (`set`, поле 2 = `42`) | поле 2 — **литерал значения** | +| `2` | **5** | `10077`–`10085` (`expr`, поле 4 = `1`/`-1`) | второй операнд **expr** — литерал | + +Поле 5 — **производное**: «в соответствующем поле литерал, а не id». В YAML **не пишется**, +энкодер восстанавливает сам. + +| Точка | Что | +|---|---| +| `config-to-yml.py` (`~938`) | ветка «поле 2 = число» больше **не** пишет `args` — только `value` | +| `yml-to-config.py` (`~688`) | вместо разбора `args` возвращает `[59, 'set <имя>', val, 0, 1]` | + +```yaml + - id: 10069 + set_var: + name: varname + value: 42 +``` + +Круг ✅ **9/9 чистый**; `args` в артефакте — ровно **4**, все законные (`objcmd`: `descr`+`args`+`target`). + +**Коммит:** **`4b99d8e`** («set_var: args убран у числового поля 2, поле 5 ставится само»). + +🔴 **ПИТФОЛЛ 86 — «хвост полей» ≠ «args».** Прежде чем склеивать поля 3..n в `args`, проверить по +**всем** записям типа, одинаково ли они устроены. Здесь поле 5 имело смысл (признак литерала), и +склейка создала ключ, которого в конфиге нет. Признак ошибки: значение есть **ровно у одной записи** +из десятков (`1` из 87) — это сигнал «производное поле, спросить по данным», а не «хвост». + +### 21.4. ✅ Круг 38 — `action` развёрнут, шаги ушли из `scenario_orphans` (ЗАМЕНЁН кругом 39) **Коммит `6d0b6a5`** («action: тела шагов развёрнуты на месте, шаги ушли из scenario_orphans»): +форма `action:` затем отвергнута (§21.3) и заменена на `trigger.step_id` в `8f2edd4`. Коммит +оставлен в истории — он не ломал круг и не терял данные. + +**Актуальная форма trigger-сценария — §21.3.** Ниже — история. | Точка | Что | |---|---|