From 4736965eff3e0ba59b471f8da92bad67892d6812 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 17 Sep 2026 23:41:33 +0600 Subject: [PATCH] [2026-09-17] eagle: personal/projects/zont-config-compiler.md --- personal/projects/zont-config-compiler.md | 157 +++++++++++++++++++++- 1 file changed, 154 insertions(+), 3 deletions(-) diff --git a/personal/projects/zont-config-compiler.md b/personal/projects/zont-config-compiler.md index 549fd386..1eadf031 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-17k43' +updated: '2026-09-18' tags: - personal - zont @@ -58,6 +58,15 @@ aliases: - ZONT дубли id scenario_step - ZONT curl config.txt - ZONT стянуть конфиг + - ZONT авто-id + - ZONT автогенерация id + - ZONT next_id max+1 + - ZONT objcmd + - ZONT objcmd source + - ZONT objcmd args + - ZONT три точки входа энкодера + - ZONT круг 44 + - ZONT рабочее дерево 2026-09-18 related: - '[[family/tech/zont-api]]' - '[[family/how-to/home-automation]]' @@ -3075,9 +3084,12 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и | 4 | ✅ **ЗАКРЫТО** — `args: [0, 1]` у `#Z10069` была выдумкой; поле 5 записи 59 производное, энкодер ставит сам (§21.5, `4b99d8e`) | — | | 5 | Поле 4 = `2` у `expr` (`10077`–`10085`) — то же производное поле, что поле 5 (§21.5). Имени не дано, энкодер восстанавливает по признаку «второй операнд — литерал» | форма не согласована | | 6 | Push в Gitea — **15** коммитов не запушены | по команде Alex | +| 7 | **`objcmd`: `args` → `source`** — форма согласована, в код НЕ внесена (§26) | делать (план готов) | +| 8 | **Авто-id** — согласовано, план готов (§25), не реализовано | делать (нужен ответ про верхнюю границу `20556`) | -> ✅ Единственный открытый пункт — 5 (поле 4 у `expr`), и он **не блокирует** круг: 9/9 зелёный, -> все открытые ранее вопросы закрыты. Всё в этом доке проверено на 9 конфигах. +> ⚠️ Пункты **7** и **8** добавлены 2026-09-18 — оба согласованы, но не реализованы. +> ✅ Единственный «нерешённый по форме» пункт — 5 (поле 4 у `expr`), и он **не блокирует** круг: +> 9/9 зелёный. Всё в этом доке проверено на 9 конфигах. --- @@ -3448,7 +3460,146 @@ echo "exit=$?"; ls -la zont_config/config_X.yml | `#Z10447=*` | маркер пустого объекта, встречается в конфиге | | Маска дней `109` | `0b1101101` = пн, ср, чт, сб, вс (§10.1 круг 16) | +--- +## 24. 🔄 Рабочее дерево на начало сессии 2026-09-18 (НЕ закоммичено) + +Состояние **отличается** от §21.2 (который описывает конец круга 43). Проверено `git log`/`git status` +в начале следующей сессии: + +| | | +|---|---| +| **HEAD** | **`5fdedc9`** — «type 5 в шаге: action вместо хвоста params, отдельная секция сборки» | +| **`origin/main`** | **`4b99d8e`** → **`5fdedc9` НЕ запушен** | +| **Модифицированы** | `config-to-yml.py`, `yml-to-config.py`, `zont_config/config_local_2026-09-17_23-21-20.yml` | +| **Untracked** | `audit2.py`, `audit_8456.py`, `check_ops.py`, `chk_extra.py`, `dump_new_types.py`, `fit_temp.py`, `fit_temp2.py`, `probe_sched.py`, `probe_types.py`, `read_scenarios.py`, `trace_scenarios.py`, `verify_answers.py`, `why_raw.py`, `zont_api_docs/`, `zont_local_ui_recon/` | +| **Артефакт** | `config_local_2026-09-17_23-21-20.yml` — 90 918 байт, mtime `Sep 17 23:30:52` (на 55 байт меньше, чем в §21.2: **90 973 → 90 918**, расхождение не объяснено) | +| **В `zont_config/`** | два `.yml`: `..._22-26-26.yml` (90 480) и `..._23-21-20.yml` (90 918) | + +⚠️ **Правило:** §21.2 описывает **коммиты**, §24 — **рабочее дерево**. На старте сессии — всегда +`git status`, не полагаться на «всё закоммичено». + +🔴 **ПИТФОЛЛ 91 — код не является источником истины о своём поведении.** На вопрос «а разве в коде +уже нет авто-расчёта id?» я собирался ответить по памяти («нет, и это подтверждено grep'ом»). +Фактическая проверка: `grep -nE "auto|_next|next_id|max\(|alloc|assign" yml-to-config.py` — **пусто**, +авто-id действительно нет. Но **находка рядом оказалась важнее**: `objcmd` собирается в **двух** +точках (`emit_step` ~990 и `emit_action` ~770), и покрыта была только одна. Гипотезу про код +проверять `grep` по **всем** точкам входа, а не по одной. + +--- + +## 25. 🎯 План: авто-id для всех сущностей (СОГЛАСОВАНО, не реализовано) + +**Запрос Alex:** «я предполагал что id генерятся автоматом для всех сущностей если не прописаны явно». + +### 25.1. Состояние кода (проверено, не по памяти) + +| Факт | Деталь | +|---|---| +| Аллокатора **нет** | `grep -nE "auto\|_next\|next_id\|max(\|alloc\|assign"` в `yml-to-config.py` — **0 строк** | +| Есть только **читатель** id | `_collect_yaml_ids` (строка 68) собирает уже прописанные id в `_known_ids`, чтобы отличать литерал от ссылки. Если id нет — в множество не попадёт, дальше `obj['id']` падает | +| **17 сырых обращений** `obj['id']` | строки `105, 108, 149, 154, 161, 189, 198, 203, 210, 222, 225, 1213, 1219, 1230, 1235, 1242, 1268` — жёсткие, не `.get()` | +| Единственный «дефолт» | `config_id = config.get('id', 0)` (строка 99) — это **`0`**, не сгенерированный id. `#Z0=36,...` прибор не примет | + +### 25.2. 🔴 Правило нумерации прибора — ВЫВЕДЕНО ИЗ ДАННЫХ + +``` +next_id = max(все существующие id) + 1 ← НЕ «первая свободная дыра» +``` + +**Доказательство:** Alex добавил 75 объектов через UI. Прибор выдал им id **`10371..10447` подряд, +без дыр**, продолжая от предыдущего максимума `10370`. Разрывы `10441→10443` и `10445→10447` — это +`10442`/`10446`, которые он **удалил** (остались в конфиге как `end`-терминаторы). + +| Проверка | Результат | +|---|---| +| max id в конфиге | **`20555`** | +| id `20551`/`20554`/`20555` | **железо** (сопроцессоры, радиомодуль) — выдано прибором, не человеком | +| «пользовательские» id | диапазон `4098..12109`, плотно, с дырами | +| Дыр всего | **19 484** — прибор их **не использует** | + +### 25.3. План + +| # | Шаг | Оценка | +|---|---|---| +| 1 | Разведка: как `emit_step`/`emit_action`/`emit_condition` + `scenario_orphans` разрешают ссылки | 5 мин | +| 2 | Аллокатор: `_ids = _known_ids ∪ {id из всех секций}`; `_next = max(_ids)+1`; `alloc()` с инкрементом. **Фаза 1** — пройти секции и проставить id всем записям без `id` (записать **в саму запись**, чтобы ссылки нашли) | 20 мин | +| 3 | Ссылки на объекты без id — **вариант (в): разрешить только листья** (объект ни на что не ссылается). Покрывает ~90% (датчик, действие, регистр), не ломает модель ссылок | 30 мин, риск | +| 4 | Заменить 17 точек `obj['id']` → id из аллокатора. Простые: `relays`, `heating_circuits`, `sensors`, `analog_outputs`, `marker_entries`, `modbus_devices`. Сложные — сценарии (id нужен **до** эмиссии шагов) | 40 мин | +| 5 | Круг 9/9 — регресс обязателен (старые конфиги все с id, поведение не должно измениться) | 5 мин | +| 6 | Тест автогенерации: из артефакта удалить `id` у 3 объектов, собрать, проверить назначение и сходимость | 10 мин | + +**Итого ~2 часа**, из них час — шаг 3. + +**Риски:** (а) ссылки в новые объекты — реальная проблема, требует решения шага 3; (б) порядок +эмиссии: `S`-блоки первыми, `Z` — после, id нужен раньше строки; (в) **верхняя граница** — +`max+1 = 20556` перескочит железо, вопрос к Alex открыт. + +--- + +## 26. 🔴 `objcmd`: `args` — СТАРОЕ поле декодера, энкодер его молча проносит (2026-09-18) + +**Жалоба Alex:** «я все еще вижу `args: - 8472`» — при том, что форма уже согласована как вариант A +(источник отдельным ключом, не `args`). + +### 26.1. Что в артефакте и что в сырье + +| id | Сырая строка | YAML сейчас | +|---|---|---| +| `10371` | `#Z10371=59,'objcmd 8700 "1 %0"',14.5,0,1` | `target: 8700`, `args: [14.5]` | +| `10375` | `#Z10375=59,'objcmd 9102 "6,%0";#a',10374,0,0` | `target: 9102`, `args: [10374]` | +| `10398` | `#Z10398=59,'objcmd 8382 "1 %0"',8472,0,0` | `target: 8382`, `args: [8472]` | + +`args` — это **поле 3 записи**, прочитанное как «список аргументов». У `10371` — литерал `14.5`, +у `10398` — **ссылка на объект** (`#Z8472=59,'set var1',0,0,0` существует). Одно имя `args` покрывает +и литерал, и ссылку → читатель не может отличить. + +### 26.2. Корень: зеркало бага из §6 доки `roundtrip-key-verification` + +**`objcmd` собирается в ТРЁХ точках, покрыта одна:** + +| Точка | Строка `yml-to-config.py` | Статус | +|---|---|---| +| `emit_step` | ~990 | ✅ работает: `fields = [59, cmd, *args] + f5` | +| `emit_action` | ~770 | ❌ пропускает разобранную форму (ветка `'pickle' in node` ловит всё сырое) | +| Цикл добивки по `scenario_scripts` | 1245–1290 | ❌ **нет ветки `objcmd`**, `raw` не выставляется — работает **по стечению** (`_emitted_ids` уже содержит `10398`) | + +**Почему круг зелёный:** `args` создаёт **декодер** (`config-to-yml.py:1011`, +`node['args'] = [_oc_arg]`), энкодер его читает через `step.get('args') or [0]` и байты доносит +верно. Round-trip доказывает «вывод парсера == вход», про энкодер не говорит ничего (см. док +`roundtrip-key-verification`). + +### 26.3. Целевая форма (вариант A, согласована) + +```yaml + - id: 10398 + objcmd: set_sensor # имя действия (маска 1 %0) + cmd: objcmd 8382 "1 %0" # текст команды — читаемость + обратная сборка + target: 8382 # кому адресована команда + source: 8472 # ← ОТКУДА берётся значение (поле 3 записи) +``` + +У `10371` источник — **литерал**: `source: 14.5`. Одно поле, два типа значения; читатель видит +разницу сразу: `8472` есть в конфиге как объект, `14.5` — нет. + +### 26.4. Шаги правки (~40 мин, риск низкий) + +1. Декодер `config-to-yml.py:1011` — `node['source'] = _oc_arg` вместо `node['args'] = [_oc_arg]`. ~1 строка. +2. Энкодер `yml-to-config.py:1004` — читать `source`, не `args`; поле 5 по-прежнему производное + (`source ∈ _known_ids` → `0`, иначе `1`). ~3 строки. +3. Энкодер `emit_action:770` — добавить ветку `objcmd` (та же логика, что в `emit_step`). +4. Цикл добивки 1245–1290 — добавить ветку `objcmd`, чтобы объект не проходил по стечению. +5. Круг 9/9 → перегенерировать артефакт **в целевой файл** (§22.5, питфолл 87). + +**Проверка — подмена, не read-back:** поменять `source: 8472` → `source: 8669` в **копии**, собрать, +убедиться, что изменилась **ровно одна строка** и в ней `8669`. + +### 26.5. 🔴 ПИТФОЛЛ 92 — форма согласована ≠ в код внесена + +Alex третий раз видел `args` у `objcmd` при том, что форма обсуждалась и была принята («пойдет»). +**Согласованную форму фиксировать в доке СРАЗУ при согласии, а правку кода делать в тот же заход.** +Иначе следующая сессия стартует с вопроса «почему всё ещё так», и Alex видит регресс там, где был +просто пропущенный шаг. ---