[2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-scenario-logic-11109.md work/projects/cpm-m3-telemetry-privacy-triage.md
This commit is contained in:
@@ -21,7 +21,7 @@ tags:
|
||||
- homeautomation
|
||||
title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
|
||||
type: how-to
|
||||
updated: 2026-09-17i
|
||||
updated: 2026-09-17j
|
||||
---
|
||||
|
||||
# ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml`
|
||||
@@ -146,12 +146,11 @@ updated: 2026-09-17i
|
||||
|
||||
### Как это выглядит в YAML
|
||||
|
||||
> ⚠️ **ТЕКУЩАЯ форма (коммит `199f2b1`).** Переделывается по **§5c** — там одна вложенная форма
|
||||
> `blocks[].if/then` для всех сценариев вместо плоского дубля и отдельных секций. Ниже — как есть
|
||||
> сейчас, чтобы читать работающий код; целевая форма — в §5c.
|
||||
> ✅ **НОВАЯ форма (2026-09-17, §5c).** Все сценарии — единая вложенная структура `blocks[].if/then/else`
|
||||
> плюс блок `trigger:`. Плоские `when`/`then` и секции `steps`/`delays`/`scenario_conditions` убраны.
|
||||
> **Форма `11109` ниже — устаревшая**, оставлена для истории; актуальная — §5c.
|
||||
|
||||
Одношаговые сценарии (64 из 65) сохраняют плоский вид `when`/`then` — старые YAML не ломаются.
|
||||
Многошаговые получают список `steps`, а хвостовые задержки — `extra_links`:
|
||||
**Устаревшая форма (коммит `199f2b1`)** — плоский `when`/`then` для 1-шаговых + `steps` + `extra_links`:
|
||||
|
||||
```yaml
|
||||
scenarios:
|
||||
@@ -384,20 +383,12 @@ diff A.txt B.txt # пусто = round-trip чистый
|
||||
|
||||
---
|
||||
|
||||
## 5c. 🔄 Переработка структуры YAML сценариев — 🟢 СОГЛАСОВАНО, парсер дописывается (2026-09-17)
|
||||
## 5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17
|
||||
|
||||
> ✅ **СТАТУС (обновлено 2026-09-17, вечер): Alex дал «дописывай парсер».** Семантика типов
|
||||
> 47/48/50/59 **раскрыта** (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7),
|
||||
> таблица операторов и логики подтверждена фактами конфига. Работа по §5c разрешена.
|
||||
>
|
||||
> ⚠️ **Но остались 8 непрозрачных элементов** конкретно в сценарии `8456`
|
||||
> (`#8458`, `#8463`, `#8465`, `#8467`, `#8486`, `#8488`, `#9500`, `#8457`/`#8470`).
|
||||
> Alex спросил «всё ли кристально прозрачно?» — ответ: **нет**. Вопросы заданы, ответа ещё нет
|
||||
> (полный список — [[family/tech/zont-scenario-logic-11109]] §8.10).
|
||||
>
|
||||
> 📌 **Решение по стратегии парсера:** сохранять байт-в-байт всё, что не понято (скрипты `59`
|
||||
> как сырые строки, суффиксы `;#a`/`;#h`/`;#p`, отсутствующие id — как есть), без «умного» перевода.
|
||||
> Тогда round-trip чистый, а непонятное не портится.
|
||||
> ✅ **СТАТУС (2026-09-17, ночь): парсер и энкодер дописаны, round-trip чистый на 6 конфигах.**
|
||||
> Семантика типов 47/48/50/59 раскрыта (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7),
|
||||
> операторы и логика подтверждены фактами конфига. Код реализован.
|
||||
> **Не закоммичено** — ждёт команды Alex.
|
||||
|
||||
**Задача Alex (исходная):** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов».
|
||||
|
||||
@@ -411,58 +402,96 @@ Alex отклонил:
|
||||
**Правило:** в ZONT **нет триггеров** — роль триггера играет само изменение реле, а сценарий читается
|
||||
как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA.
|
||||
|
||||
### Утверждённая форма (предложена, ждёт финального ОК по двум вопросам)
|
||||
### ✅ Реализованная форма
|
||||
|
||||
```yaml
|
||||
scenarios:
|
||||
- id: 11109
|
||||
name: 'Передернуть Автомат Котельной'
|
||||
enabled: false
|
||||
- id: 8456
|
||||
name: Простой тестовый сценарий
|
||||
trigger: {type: schedule} # manual | trigger | schedule | interval
|
||||
_raw_trigger_kind: 8 # исходное поле 5
|
||||
blocks:
|
||||
- id: 11827 # шаг (46)
|
||||
- id: 8506 # шаг (46)
|
||||
if:
|
||||
id: 11823 # условие (49), id опционален
|
||||
relay: 11190
|
||||
operator: equals # equals | not_equals
|
||||
value: 0
|
||||
id: 8496
|
||||
group: and # and | or | not
|
||||
children:
|
||||
- {id: 8493, op: '<', left: 8492, value: 3}
|
||||
- {id: 8495, op: '>=', left: 8472, right: 8494}
|
||||
then:
|
||||
- {id: 11191, action: relay_on, relay: 11190}
|
||||
- {id: 11030, action: relay_off, relay: 11028}
|
||||
- {id: 11824, action: wait, ms: 20000}
|
||||
- {id: 11029, action: relay_on, relay: 11028}
|
||||
- {id: 11825, action: wait, ms: 60000}
|
||||
- {id: 11192, action: relay_off, relay: 11190}
|
||||
- {id: 11826, action: wait, ms: 0}
|
||||
tail: # хвостовые задержки из поля 2
|
||||
- {id: 11828, action: wait, ms: 0}
|
||||
- id: 8505
|
||||
kind: 1
|
||||
if: {id: 8501, group: or, children: [...]}
|
||||
then:
|
||||
- {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]}
|
||||
else:
|
||||
- {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]}
|
||||
extra_links: [8457, 8458, ..., 8507] # прочие ссылки поля 2, в исходном порядке
|
||||
_raw_links: [8457, ..., 8506, 8507] # точный порядок поля 2 (для байт-tочности)
|
||||
```
|
||||
|
||||
**Что меняется по сравнению с текущим YAML:**
|
||||
**Маппинг типов на YAML:**
|
||||
|
||||
| Сейчас (разбросано) | Станет |
|
||||
|---|---|
|
||||
| `scenarios[].when` / `.then` — плоский дубль для 1-шаговых | убрано: **одна форма** `blocks[].if/.then` для всех 65 |
|
||||
| `scenarios[].steps[]` + отдельные `delays` / `scenario_conditions` / `scenario_steps` | одна вложенная структура `blocks` |
|
||||
| `wait` лежит отдельной секцией верхнего уровня | `wait` — **внутри** `then`, по месту срабатывания |
|
||||
| `extra_links: [11828]` — голые id | `tail:` с блоками `wait` |
|
||||
| Тип | В YAML | Пример |
|
||||
|---|---|---|
|
||||
| `11` | `trigger: {type, …}` + `blocks[]` | `trigger: {type: schedule, days: [mon,…], time: '13:26'}` |
|
||||
| `46` | `blocks[].{if, then, else}` | `{id, kind?, if, then: […], else?: […]}` |
|
||||
| `47` | `{op, left, value}` или `{op, left, right}` | `{id: 8493, op: '<', left: 8492, value: 3}` |
|
||||
| `48` | `{group, children}` | `{id: 8496, group: and, children: […]}` |
|
||||
| `49` | `{condition: {object, operator, value}}` | старая форма, поддержана |
|
||||
| `59` | `{script: <код дословно>, args: […]}` | `{id: 8502, script: 'puts "then-text"'}` |
|
||||
| `50`, неизвестные | `raw: […]` | `{id: 8548, raw: [50, 1, 0, 0, 109]}` |
|
||||
|
||||
**Имена ключей и нулевые паузы — решено, не переспрашивать:**
|
||||
- Ключи: **`blocks` / `if` / `then`** — ближе к формулировке Alex «вложенные инструкции если … то …».
|
||||
- Нулевые паузы (`ms: 0`) — **остаются как `wait`**. Честно отражают конфиг, нужны для round-trip.
|
||||
- `id` у операторов — **опциональные**: пишутся, когда есть в конфиге; отсутствуют, когда нет.
|
||||
**Операторы type 47 (порядок UI `<, >, =, <=, >=`):** `0`=`<`, `1`=`>`, `2`=`=`, `3`=`<=`, `4`=`>=`.
|
||||
**Логика type 48:** `0`=`and`, `1`=`or`, `2`=`not`.
|
||||
|
||||
**План работ:**
|
||||
1. `config-to-yml.py` — блок type 11: генерировать `blocks[].if/then`; `wait` (45) встраивать в `then`; `tail` для хвостовых
|
||||
2. `yml-to-config.py` — обратный разбор `blocks`; **поддержать старые формы** (`when`/`then`, `steps`) для совместимости
|
||||
3. `test_roundtrip.py` — все 4 конфига должны остаться чистыми
|
||||
4. Доки — обновить §2.1 этой заметки
|
||||
**Триггер (поле 5 типа 11):**
|
||||
|
||||
**Обязано сохраниться байт-в-байт (риск round-trip):**
|
||||
| Поле 5 | `trigger.type` | Доп. поля |
|
||||
|---|---|---|
|
||||
| `0`/`1` | `manual` | `enabled: bool` |
|
||||
| `8` | `schedule` | `days[]`, `days_mask`, `time`, `time_raw` (поле 4 = маска дней, бит 0=ПН; поле 6 = `(час<<8)\|мин`) |
|
||||
| `9` | `trigger` | — |
|
||||
| `10` | `interval` | `interval_ms` |
|
||||
|
||||
### Реализация (код)
|
||||
|
||||
**`config-to-yml.py`** — рекурсивные хелперы `dump_step()` / `dump_condition()` / `dump_leaf()` /
|
||||
`dump_action()`; сборщики `scenario_scripts` / `scenario_conditions` / `scenario_raw_objects`;
|
||||
`_raw_links` для точного порядка поля 2; **fixed-point sweep** операндов (`left`/`right`/`args`) —
|
||||
без него теряются скрипты второго порядка (напр. `8459`/`8460` через `8462`); неизвестные типы →
|
||||
`raw_objects` вместо падения (там же `KNOWN_TYPES += 47,48,50,59`).
|
||||
|
||||
**`yml-to-config.py`** — обратные `emit_step()` / `emit_condition()` / `emit_action()`; поддержка
|
||||
старых форм (`steps`/`when`/`then`) через `_scenario_from_legacy()`; восстановление порядка ссылок
|
||||
из `_raw_links`; новые секции `scenario_scripts`/`scenario_conditions`/`scenario_raw_objects`/`raw_objects`
|
||||
в `TYPE_ORDER`.
|
||||
|
||||
### 🔴 Три питфолла реализации (round-trip ловил каждый)
|
||||
|
||||
| # | Проблема | Причина | Решение |
|
||||
|---|---|---|---|
|
||||
| 1 | Объекты не попадали в вывод | `result_lines` в энкодере — **фильтрованное подмножество**: объект должен быть и в `lines`, и в `TYPE_ORDER` | Добавить секции в `TYPE_ORDER` |
|
||||
| 2 | Операнды терялись | `left`/`right`/`args` — **не рёбра дерева**, обход их не видит | Fixed-point sweep: собирать референсы из `raw`-тел, докидывать, повторять |
|
||||
| 3 | exit 4 «Duplicate ID» | Секции эмитились дважды (явный цикл + `TYPE_ORDER`) | Убрать явный цикл; типы 5/9/3/0/36 не регистрировать в `raw_objects` (у них свои секции) |
|
||||
|
||||
**Обязано сохраниться байт-в-байт (сохранено):**
|
||||
- порядок объектов в файле (45 идёт **после** 11, но **до** 46)
|
||||
- `extra_links` — хвостовые задержки в поле 2 сценария
|
||||
- число полей (7 vs 8) и `_raw_*`-поля (`_raw_field_count`, `_raw_field3`, `_raw_divider`) — **не удалять**
|
||||
- порядок ссылок поля 2 сценария → `_raw_links`
|
||||
- число полей (7 vs 8) и `_raw_*`-поля (`_raw_field_count`, `_raw_trigger_kind`, `_raw_trigger_params`) — **не удалять**
|
||||
|
||||
**Бэкап:** коммит `199f2b1` уже есть — откат возможен в любой момент, отдельных копий не делать.
|
||||
### ✅ Round-trip — 6/6 конфигов чисто
|
||||
|
||||
```bash
|
||||
cd /Users/admin/Automation/HA-ZONT-Modbus
|
||||
python3 test_roundtrip.py zont_config/config_local_2026-09-17_14-16-35.txt # ✅ 685/685
|
||||
python3 test_roundtrip.py zont_config/config_0FA7C33CC89F_…_12-12-28.txt # ✅
|
||||
for f in zont_config/archive/*.txt; do python3 test_roundtrip.py "$f"; done # ✅ -2, -3, -4
|
||||
python3 test_roundtrip.py zont_local_ui_recon/config_live_192.168.0.50.txt # ✅
|
||||
```
|
||||
|
||||
**Бэкапы кода:** `/tmp/config-to-yml.py.bak`, `/tmp/yml-to-config.py.bak` (сняты перед правкой).
|
||||
**Откат:** git (`199f2b1` — последний коммит).
|
||||
|
||||
---
|
||||
|
||||
@@ -617,67 +646,64 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
|
||||
|
||||
---
|
||||
|
||||
## 5i. 🔴 Конструктор логики ZONT (типы 47/48/50/59) — НЕ ПОДДЕРЖАН
|
||||
## 5i. 🔴 Конструктор логики ZONT (типы 47/48/50/59) — ✅ ПОДДЕРЖАН 2026-09-17
|
||||
|
||||
> **Найдено 2026-09-17.** Alex создал в UI тестовые сценарии («все доступные триггеры и варианты
|
||||
> логики»). Разбор локального конфига `config_local_2026-09-17_14-16-35.txt` (660 `#Z`) вскрыл
|
||||
> **визуальный конструктор логики** со скриптовым движком.
|
||||
|
||||
**Конвертер на этом конфиге падает:**
|
||||
|
||||
```bash
|
||||
$ python3 config-to-yml.py zont_config/config_local_2026-09-17_14-16-35.txt > /tmp/new.yml
|
||||
Ошибка: Условие 8496: не type 49
|
||||
EXIT=2
|
||||
```
|
||||
>
|
||||
> ✅ **Статус: поддержан.** Парсер и энкодер дописаны (§5c), round-trip чистый. Ранее падал с
|
||||
> `Ошибка: Условие 8496: не type 49` (exit 2) — **больше не падает**.
|
||||
|
||||
**Две ключевые находки:**
|
||||
|
||||
1. 🔴 **Поле 5 типа 11 — НЕ `enabled`, а ТИП ЗАПУСКА:** `0`/`1` ручной, `8` расписание,
|
||||
`9` триггер, `10` интервал. Поле 6 — параметр (для интервала — мс). **Текущий конвертер
|
||||
теряет эту информацию** (сводит к булеву `enabled`).
|
||||
2. **Новые типы:** `47` (лист условия), `48` (группа И/ИЛИ), `50` (запуск по id),
|
||||
`59` (мини-скрипт: `objcmd`/`objstate`/`expr`/`set`/`puts`/`storeev`).
|
||||
`9` триггер, `10` интервал. Поле 6 — параметр (для интервала — мс; для расписания — `(час<<8)|мин`,
|
||||
поле 4 — маска дней недели, бит 0=ПН).
|
||||
2. **Новые типы:** `47` (лист условия: `op, left, value|right`), `48` (группа `and`/`or`/`not`),
|
||||
`50` (объект-триггер, хранится как `raw`), `59` (мини-скрипт: `objcmd`/`objstate`/`expr`/`set`/
|
||||
`puts`/`storeev`; код и args сохраняются **дословно**).
|
||||
|
||||
**Что нужно сделать** (семантика получена — [[family/tech/zont-scenario-logic-11109]] §8.7;
|
||||
работа разрешена Alex'ом 2026-09-17 вечером, «дописывай парсер»):
|
||||
парсер 47/48/50/59 с рекурсивным деревом условий; поле 5 хранить как тип запуска (плюс маска дней
|
||||
и время для расписания); принимать непустое поле 4 шага; допускать не-46 типы в поле 2.
|
||||
**Операторы type 47 (порядок UI `<, >, =, <=, >=`):** `0`=`<`, `1`=`>`, `2`=`=`, `3`=`<=`, `4`=`>=`.
|
||||
|
||||
> 📌 **Стратегия:** всё непонятое (скрипты `59` дословно, суффиксы `;#a`/`;#h`/`;#p`, отсутствующие id)
|
||||
> сохранять байт-в-байт, без «умного» перевода — round-trip останется чистым. 8 открытых вопросов
|
||||
> по сценарию `8456` — [[family/tech/zont-scenario-logic-11109]] §8.10.
|
||||
> 📌 **Стратегия (реализована):** всё непонятое (скрипты `59` дословно, суффиксы `;#a`/`;#h`/`;#p`,
|
||||
> отсутствующие id, `type 50`) сохраняется байт-в-байт через `raw` / `unresolved` — без «умного»
|
||||
> перевода. Round-trip остаётся чистым, а непонятное не портится.
|
||||
|
||||
➡️ **Полный разбор — [[family/tech/zont-scenario-logic-11109]] §8**
|
||||
|
||||
---
|
||||
|
||||
## 6. Состояние проекта (проверено 2026-09-17, вечер)
|
||||
## 6. Состояние проекта (проверено 2026-09-17, ночь)
|
||||
|
||||
✅ **Конвертеры закоммичены — `199f2b1`.** Рабочее дерево чистое (после этого коммита).
|
||||
✅ **Конвертеры переписаны под сценарный конструктор (§5c).** Рабочее дерево — **не закоммичено**,
|
||||
ждёт команды Alex. Последний коммит — `199f2b1`.
|
||||
|
||||
⚠️ **Но появился конфиг, который конвертер НЕ разбирает** — с конструктором логики (§5i).
|
||||
✅ **Round-trip чистый на 6/6 конфигах** (включая новый локальный с 660 `#Z` и legacy 7-поле `-2`).
|
||||
|
||||
| Файл | Статус |
|
||||
|---|---|
|
||||
| `config-to-yml.py`, `yml-to-config.py` | ✅ **изменены и закоммичены** (`199f2b1`, §5b) |
|
||||
| `test_roundtrip.py` | ✅ **новый файл**, в коммите `199f2b1` |
|
||||
| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежий конфиг с контроллера, **не в git** (`untracked`) |
|
||||
| одноимённый `.yml` | ✅ **пересобран, 80 472 байта / 4320 строк / 24 секции**, YAML валиден. **не в git** |
|
||||
| `zont_config/archive/` | **не в git** (`untracked`) — лежит в `.gitignore`-нейтральном состоянии |
|
||||
| `zont_config/H2000_PRO_config_actual-{2,3,4}.txt` / `.yml` | ✅ **перемещены в `archive/` и закоммичены** (`7ae0e32`) |
|
||||
| `zont_api_docs/` | ✅ **новая папка** — локальная копия доки облачного API ZONT (`zont_api_docs.html` 250 KB, `zont_api_docs.txt` 99 KB, `convert.py`). **не в git**. Исследование — [[family/tech/zont-api]] |
|
||||
| `zont_config/config_local_2026-09-17_14-16-35.txt` | 🆕 **34 907 байт, 660 `#Z`, 25 `#S`** — конфиг с **тестовыми сценариями** Alex (все виды триггеров/логики). Снят с `http://192.168.0.50/config.txt`. **не в git** |
|
||||
| `dump_new_types.py`, `probe_types.py`, `trace_scenarios.py` | 🆕 **новые скрипты разбора** сценарных типов 47/48/50/59. Разбор — [[family/tech/zont-scenario-logic-11109]] §8.8. **не в git** |
|
||||
| `read_scenarios.py` | 🆕 **читаемый дамп сценария** — дерево если/то/иначе с именами объектов. Токенайзер `split_top()` учитывает кавычки/`\`/`[]`. **не в git** |
|
||||
| `probe_sched.py`, `verify_answers.py`, `check_ops.py`, `audit_8456.py`, `audit2.py` | 🆕 **проверочные скрипты** — расписание, ответы Alex против конфига, операторы, аудит 8456. **не в git** |
|
||||
| `~/rasputin-tmp/zont-util/` | ✅ **новая папка на Mac** (вне репо) — настроечная утилита `H1000 Programmator` 2.8.5, драйвер USB, прошивка `.enc`, скрипт распаковки `extract.py`. Разбор — §5g-2, [[family/tech/zont-api]] §10 |
|
||||
| `~/rasputin-tmp/zont-{auth-probe,recon,recon2,ws-probe}.js` | ✅ скрипты разведки локального WS-интерфейса. **не в git** |
|
||||
| `config-to-yml.py` | 🆕 **изменён** — парсер типов 47/48/50/59, `blocks/if/then/else`, триггер, raw-фоллбэк (§5c). **не закоммичено** |
|
||||
| `yml-to-config.py` | 🆕 **изменён** — обратные `emit_*`, `_raw_links`, новые секции в `TYPE_ORDER` (§5c). **не закоммичено** |
|
||||
| `test_roundtrip.py` | ✅ в коммите `199f2b1`, без изменений |
|
||||
| `zont_config/config_local_2026-09-17_14-16-35.txt` | 🆕 **34 907 байт, 660 `#Z`, 25 `#S`** — конфиг с **тестовыми сценариями** Alex. Снят с `http://192.168.0.50/config.txt`. **не в git** |
|
||||
| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежий боевой конфиг, **не в git** |
|
||||
| `zont_config/archive/` | `-2`/`-3`/`-4` — ✅ **закоммичены** (`7ae0e32`) |
|
||||
| `zont_local_ui_recon/config_live_192.168.0.50.txt` | живой конфиг для разведки WS-интерфейса, **не в git** |
|
||||
| `zont_api_docs/` | ✅ локальная копия доки облачного API (`zont_api_docs.html`, `.txt`, `convert.py`). **не в git** |
|
||||
| `read_scenarios.py` | 🆕 **читаемый дамп сценария** — дерево если/то/иначе с именами. Токенайзер `split_top()`. **не в git** |
|
||||
| `dump_new_types.py`, `probe_types.py`, `trace_scenarios.py` | 🆕 скрипты разбора сценарных типов. Разбор — [[family/tech/zont-scenario-logic-11109]] §8.8. **не в git** |
|
||||
| `probe_sched.py`, `verify_answers.py`, `check_ops.py`, `audit_8456.py`, `audit2.py`, `chk_extra.py` | 🆕 проверочные скрипты. **не в git** |
|
||||
| `~/rasputin-tmp/zont-util/` | ✅ настроечная утилита `H1000 Programmator` 2.8.5, прошивка `.enc`, `extract.py`. Разбор — §5g-2 |
|
||||
| `~/rasputin-tmp/zont-{auth-probe,recon,recon2,ws-probe}.js` | ✅ скрипты разведки локального WS. **не в git** |
|
||||
|
||||
Не запушено: `origin/main..HEAD` = 3 коммита (`199f2b1`, `7ae0e32`, `12ba22b`).
|
||||
**Бэкапы кода перед правкой §5c:** `/tmp/config-to-yml.py.bak`, `/tmp/yml-to-config.py.bak`.
|
||||
|
||||
Не запушено: `origin/main..HEAD` = 3 коммита (`199f2b1`, `7ae0e32`, `12ba22b`). Плюс **новые правки
|
||||
§5c — незакоммичены**.
|
||||
|
||||
> ✅ **Пустой `.yml` (0 байт) больше не актуален** — причина была в падении на сценарии `11109`,
|
||||
> которое исправлено (§5b). Файл пересобран, 598 объектов.
|
||||
> исправлено (§5b).
|
||||
|
||||
**Что НЕ в git и почему:** `.txt` свежего конфига и его `.yml` — рабочие артефакты конвертации,
|
||||
Alex их не добавлял. Не коммитить без команды.
|
||||
|
||||
@@ -19,7 +19,7 @@ tags:
|
||||
- homeautomation
|
||||
title: 🔁 ZONT — сценарная логика и конструктор (11/46/47/48/49/50/59/45)
|
||||
type: tech
|
||||
updated: '2026-09-17e'
|
||||
updated: '2026-09-17f'
|
||||
---
|
||||
|
||||
# 🔁 ZONT — сценарная логика и конструктор логики
|
||||
@@ -441,10 +441,15 @@ python3 dump_new_types.py # все сценарные объекты
|
||||
> внутри кавычек (`objcmd 9102 "6,%0";#a` разваливалось). Новый `split_top()` учитывает
|
||||
> кавычки, экранирование `\` и вложенные `[]` — теперь скрипты печатаются как есть.
|
||||
|
||||
### 8.10. ⏳ НЕПРОЗРАЧНЫЕ ЭЛЕМЕНТЫ СЦЕНАРИЯ 8456 (вопросы заданы Alex 2026-09-17, ответа нет)
|
||||
### 8.10. ⏳ НЕПРОЗРАЧНЫЕ ЭЛЕМЕНТЫ СЦЕНАРИЯ 8456 (✅ разблокированы raw-стратегией)
|
||||
|
||||
Alex спросил: «а по всем действиям в "Простой тестовый сценарий" — тебе всё кристально прозрачно?»
|
||||
Ответ — **нет**. Ниже ровно то, что не выводится из конфига. **Не строить гипотезы — ждать ответа.**
|
||||
Ответ был — **нет**. Ниже ровно то, что не выводится из конфига.
|
||||
|
||||
> ✅ **Разблокировано 2026-09-17.** Alex решил: «допиливаешь парсер с известными типами, а неизвестные
|
||||
> — пусть в yml будут raw values, и тогда я доразберу». После этого конвертер дописан, round-trip
|
||||
> чистый, **эти вопросы больше не блокируют работу** (§8.9). Семантику доразбирает Alex в YAML
|
||||
> через `raw`-поля. **Гипотезы по-прежнему не строить** — открытые пункты остаются открытыми.
|
||||
|
||||
| # | Элемент | Что неясно |
|
||||
|---|---|---|
|
||||
@@ -472,23 +477,21 @@ Alex спросил: «а по всем действиям в "Простой т
|
||||
(type 59 как сырые строки с `arg1..arg3`, отсутствующие id — как есть). Round-trip останется
|
||||
байт-точным независимо от того, понята семантика или нет.
|
||||
|
||||
### 8.9. Состояние: конвертер на новом конфиге ПАДАЕТ
|
||||
### 8.9. ✅ Состояние: конвертер новый конфиг РАЗБИРАЕТ (2026-09-17)
|
||||
|
||||
```bash
|
||||
$ python3 config-to-yml.py zont_config/config_local_2026-09-17_14-16-35.txt > /tmp/new.yml
|
||||
Ошибка: Условие 8496: не type 49
|
||||
EXIT=2
|
||||
```
|
||||
> ✅ **ИСПРАВЛЕНО.** Все пять пунктов ниже реализованы в конвертерах (§5c в
|
||||
> [[family/how-to/zont-config-compiler]]). Round-trip чистый на 6/6 конфигах.
|
||||
> Ранее падал с `Ошибка: Условие 8496: не type 49` (exit 2) — **больше не падает**.
|
||||
|
||||
**Причина:** `config-to-yml.py` в блоке сценариев требует `cond[0] == 49` и
|
||||
`step[4] == []`. Новые типы (47/48/50/59) и непустое поле 4 не поддерживаются.
|
||||
**Что было нужно — ВСЁ СДЕЛАНО:**
|
||||
|
||||
**Что нужно (после ответов Alex на §8.7):**
|
||||
1. Парсер типов **47/48/50/59** + рекурсивная сборка дерева условий
|
||||
2. Поле 5 типа 11 — хранить как **тип запуска** (0/1/8/9/10) + поле 6 (параметр), не как `enabled`
|
||||
3. Поле 4 шага — принимать непустые списки
|
||||
4. Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9)
|
||||
5. Round-trip всех 5 конфигов (боевой + 3 архива + новый локальный)
|
||||
| # | Было нужно | Статус |
|
||||
|---|---|---|
|
||||
| 1 | Парсер типов **47/48/50/59** + рекурсивное дерево условий | ✅ `dump_condition`/`dump_leaf`/`dump_action` |
|
||||
| 2 | Поле 5 типа 11 — **тип запуска** (0/1/8/9/10), не `enabled` | ✅ `trigger: {type, days, time, interval_ms}` |
|
||||
| 3 | Поле 4 шага — принимать непустые списки | ✅ `blocks[].else` |
|
||||
| 4 | Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9) | ✅ `extra_links` + `_raw_links` |
|
||||
| 5 | Round-trip всех 5 конфигов | ✅ **6/6** (боевой + 3 архива + новый локальный + live) |
|
||||
|
||||
**Счётчики нового конфига (для сверки):** `660 #Z`, `25 #S`, байт — 34 907.
|
||||
|
||||
|
||||
@@ -1,94 +1,107 @@
|
||||
# Tech Design 2 / Privacy Triage: Apple — CPM M3 Telemetry
|
||||
# TD: Pixel Definition: CPM M3 Telemetry (macOS/iOS)
|
||||
|
||||
Status: **DRAFT — privacy review required**
|
||||
**Name:** Alex M · **Email:** amartemyanov@duckduckgo.com · **Objective:** O-E
|
||||
**PR:** [Add PR link]
|
||||
**Project:** https://app.asana.com/1/137249556945/project/1163321984198618/task/1216761517055116?focus=true
|
||||
**Baseline:** [[cpm-extension-health-pixels-privacy-triage]]
|
||||
**Scope:** Current implementation in `codex/cpm-failure-telemetry`, reviewed 2026-09-17. iOS 18.4+ / macOS 15.4+. Describes implemented collection, not the proposed reduced design.
|
||||
**Author:** Alex M
|
||||
**Reviewer:** TBD
|
||||
**Stakeholders:** Michal, Konrad, Russell (from the first CPM Tech Design; confirm for M3)
|
||||
**Project:** [CPM: root-cause the stuck embedded extension messaging (macOS/iOS)](https://app.asana.com/1/137249556945/task/1216761517055116)
|
||||
**Previous Tech Design:** [CPM breakage pixels](https://app.asana.com/1/137249556945/task/1218005985662983)
|
||||
|
||||
## 1. Purpose and approach
|
||||
## Background & Requirements
|
||||
|
||||
The first design detects CPM initialization failures, persistent messaging failures and recovery. M3 adds facts that help distinguish background-process failure, context-load errors and tab wiring problems.
|
||||
The first CPM pixels detect initialization failure, a stuck messaging episode and recovery. They do not explain whether a failure coincides with background-process termination, a context-load error or incorrect tab wiring.
|
||||
|
||||
An in-memory recorder observes extension lifecycle, WebKit process callbacks and critical memory pressure. When an existing initialization-failure or stuck pixel fires, it attaches a best-effort snapshot. There are no new JavaScript probes, page-content reads or periodic telemetry uploads. The delegate proxy forwards WebKit callbacks and is remotely switchable; this switch disables the proxy, **not all M3 collection**.
|
||||
Forced background-process termination can reproduce a persistent CPM outage. M3 adds native-side diagnostics to investigate whether the same failure occurs in production on iOS and macOS, without relying on the extension's messaging channel.
|
||||
|
||||
A separate new pixel reports extension reload operations that fail before the replacement context becomes active. Graveyard mitigation and A/B cohorts are outside M3.
|
||||
## Problem Statement
|
||||
|
||||
## 2. Pixels and frequency
|
||||
Distinguish background-process/context failures from tab-local problems and identify failed extension reload operations. Collect the diagnostic state alongside existing failures, without page content, URLs or persistent identifiers.
|
||||
|
||||
Names below are base names; PixelKit adds frequency/platform suffixes. iOS uses its standard platform/form-factor suffixes; macOS keeps its standard naming policy. These are not new explicit `platform` / `form_factor` payload fields. Standard metadata includes `appVersion`; macOS schemas also declare `pixelSource` and `channel`.
|
||||
M3 provides evidence for the production-validation milestone; it does not implement graveyard mitigation or an A/B experiment.
|
||||
|
||||
| Pixel | Change / trigger | Frequency |
|
||||
## Recommended Approach
|
||||
|
||||
Keep the existing CPM failure/stuck detection. A shared in-memory recorder collects lifecycle events, critical-memory-pressure notifications and current WebKit/tab state. When a failure or stuck pixel fires, attach a best-effort snapshot. No additional JavaScript probes or periodic telemetry uploads are introduced.
|
||||
|
||||
A forwarding delegate proxy observes background-process termination and responsiveness callbacks. The original delegate owns the proxy; the proxy references it weakly. A remote flag enables/disables proxy installation, not the entire diagnostics recorder.
|
||||
|
||||
| Pixel | Trigger | Frequency |
|
||||
|---|---|---|
|
||||
| `debug_web_extension_cpm_initialization_failed_after_<reason>` | Add the snapshot when an eligible finished navigation fails the existing CPM response check. | Daily |
|
||||
| `debug_web_extension_cpm_messaging_stuck_<reason>` | Add the snapshot when a later eligible navigation confirms the failure episode. | Daily + count; once per stuck episode |
|
||||
| `debug_web_extension_reload_failed` | **New:** an extension context unload/load operation fails during reload; not limited to CPM. | Daily + count |
|
||||
| `debug_web_extension_cpm_initialization_failed_after_<reason>` | Existing initialization failure; add diagnostic parameters. | Daily |
|
||||
| `debug_web_extension_cpm_messaging_stuck_<reason>` | Existing failure confirmed by a later eligible navigation; add diagnostic parameters. | Daily + count, once per episode |
|
||||
| `debug_web_extension_reload_failed` | **New:** extension unload/load fails during a reload operation. Applies to all supported extension types. | Daily + count |
|
||||
|
||||
For both CPM families, `<reason>` is `session_restoration`, `tab_crash`, `extension_reload` or `other`. The stuck reason comes from the initial failure. This includes the `stuck_extension_reload` variant omitted from the first note's table.
|
||||
`<reason>`: `session_restoration`, `tab_crash`, `extension_reload`, `other`. PixelKit applies standard frequency/platform suffixes. Standard metadata includes `appVersion`; macOS also declares `pixelSource` and `channel`.
|
||||
|
||||
Existing recovery pixels and `debug_web_extension_cpm_messaging_extension_reload_failed` gain **no** diagnostic parameters. The latter means CPM failed after a successful extension reload; the new pixel instead means the reload operation itself failed.
|
||||
Recovery pixels remain unchanged. The new reload-failure pixel reports a failed reload operation, unlike the existing `cpm_messaging_extension_reload_failed`, which reports CPM failure **after a successful reload**.
|
||||
|
||||
## 3. What is actually sent
|
||||
### Failure / stuck parameters
|
||||
|
||||
### CPM failure / stuck snapshot
|
||||
Unavailable optional facts are omitted. Boolean values are `true` / `false`.
|
||||
|
||||
Booleans are strings `true\|false`. Unavailable optional facts are omitted, not sent as false.
|
||||
|
||||
| Parameter | Collected fact / transmitted values |
|
||||
| Parameter | What is collected |
|
||||
|---|---|
|
||||
| `extension_context_loaded` | Whether the tracked context is loaded. Boolean. |
|
||||
| `background_view_alive` | Whether the recorder's current background WKWebView still exists. Boolean. |
|
||||
| `background_web_process_alive` | Whether its current process identifier is nonzero. Boolean; **PID is not sent**. |
|
||||
| `background_web_process_responsive` | WebKit's process-responsiveness verdict via SPI. Boolean. |
|
||||
| `network_process_restarted` | Current network PID differs from the value captured at context load. Boolean; not a restart count or proof of causality. |
|
||||
| `tab_known_to_webkit` | Failing tab is present in the context's open-tabs set. Boolean. |
|
||||
| `tab_controller_matches_context` | Failing tab and context use the same extension controller. Boolean. |
|
||||
| `tab_has_extension_user_scripts` | Tab's registered user scripts include the expected extension world. Boolean; does not prove execution. |
|
||||
| `native_handler_registered` | Autoconsent native-message handler is registered. Boolean; does not prove a reply was delivered. |
|
||||
| `memory_pressure_critical` | Age of last observed critical-pressure notification: `none\|under_1_min\|under_5_min\|under_30_min\|over_30_min`. No memory size is sent. |
|
||||
| `background_view_create_count` | Views created since context load: `0\|1\|2\|3_to_5\|over_5`. |
|
||||
| `background_view_leaked_count` | Tracked live views beyond the expected current view; same count buckets. Indicates retained views, not a proven leak. |
|
||||
| `extension_context_errors` | `none`, or accumulated distinct error descriptors since context load. Context errors map to names; other/underlying errors include **domain and numeric code**, not localized text. No explicit payload cap. |
|
||||
| `background_events` | Recent lifecycle sequence, described below. Omitted when empty. |
|
||||
| `extension_context_loaded` | Context is loaded — boolean. |
|
||||
| `background_view_alive` | Current background WKWebView exists — boolean. |
|
||||
| `background_web_process_alive` | Background process identifier is nonzero — boolean; PID is not sent. |
|
||||
| `background_web_process_responsive` | WebKit's process-responsiveness verdict — boolean. |
|
||||
| `network_process_restarted` | Network PID differs from the value at context load — boolean; PIDs are not sent. |
|
||||
| `tab_known_to_webkit` | Failing tab is in WebKit's open-tabs set — boolean. |
|
||||
| `tab_controller_matches_context` | Tab and context use the same extension controller — boolean. |
|
||||
| `tab_has_extension_user_scripts` | Registered scripts include the expected extension world — boolean, not proof of execution. |
|
||||
| `native_handler_registered` | Autoconsent native-message handler is registered — boolean, not proof of message delivery. |
|
||||
| `memory_pressure_critical` | Age of last observed critical pressure: `none`, `under_1_min`, `under_5_min`, `under_30_min`, `over_30_min`. |
|
||||
| `background_view_create_count` | Creations since context load: `0`, `1`, `2`, `3_to_5`, `over_5`. |
|
||||
| `background_view_leaked_count` | Retained views beyond the expected current view; same buckets. Not proof of a leak. |
|
||||
| `extension_context_errors` | `none` or accumulated error descriptors: mapped context-error names, with raw domain/code for other or underlying errors. No localized text; no explicit payload cap. |
|
||||
| `background_events` | Recent lifecycle sequence with relative ages in whole seconds. |
|
||||
|
||||
The recorder retains at most 40 lifecycle events in memory. A snapshot takes the latest 12; serialization removes oldest entries until the value fits 255 characters. Format: `event_name@-seconds`, relative to snapshot time, rounded to whole seconds.
|
||||
The recorder keeps 40 events in memory; the pixel includes at most the last 12 and 255 characters, dropping oldest entries first. Format: `event_name@-seconds`, relative to snapshot time.
|
||||
|
||||
Event names: `load`, `view`, `dealloc`, `unresponsive`, `responsive`, `proxy_on`, `proxy_off`, `died_<reason>`, `error_<descriptor>`. Death reasons: `memory_limit\|cpu_limit\|requested_by_client\|crash\|shared_crash_limit\|unknown`. Error timeline entries represent background-load errors. Names are lowercased, character-filtered and limited to 64 characters; **filtering is not an error-domain allowlist**.
|
||||
Events: `load`, `view`, `dealloc`, `unresponsive`, `responsive`, `proxy_on`, `proxy_off`, `died_<reason>`, `error_<descriptor>`. Termination reasons: `memory_limit`, `cpu_limit`, `requested_by_client`, `crash`, `shared_crash_limit`, `unknown`. Error events represent background-load failures. Event labels are lowercased, character-filtered and capped at 64 characters.
|
||||
|
||||
Example: `view@-60,died_crash@-42,error_background_failed_to_load@-41`. These are internally generated event labels, not identifiers supplied by a website.
|
||||
Example: `view@-60,died_crash@-42,error_background_failed_to_load@-41`.
|
||||
|
||||
### New reload-failure pixel
|
||||
### Reload-failure parameters
|
||||
|
||||
| Parameter | Allowed values |
|
||||
| Parameter | Values |
|
||||
|---|---|
|
||||
| `extension_type` | `embedded\|darkMode\|adBlocking\|searchToken\|unknown` |
|
||||
| `reload_trigger` | `data_clearing\|scriptlet_update\|explicit` |
|
||||
| `reload_phase` | `unload\|load\|lightweight_load\|full_load\|fallback_load` |
|
||||
| `reload_error` | `already_loaded\|not_loaded\|base_url_in_use\|no_background_content\|background_failed_to_load\|unknown\|other` |
|
||||
| `extension_type` | `embedded`, `darkMode`, `adBlocking`, `searchToken`, `unknown` |
|
||||
| `reload_trigger` | `data_clearing`, `scriptlet_update`, `explicit` |
|
||||
| `reload_phase` | `unload`, `load`, `lightweight_load`, `full_load`, `fallback_load` |
|
||||
| `reload_error` | `already_loaded`, `not_loaded`, `base_url_in_use`, `no_background_content`, `background_failed_to_load`, `unknown`, `other` |
|
||||
|
||||
Unlike `extension_context_errors`, `reload_error` is an allowlisted category: it includes no raw domain, numeric code, underlying error or message.
|
||||
These are allowlisted enums; this pixel does not send raw error domains, codes or messages.
|
||||
|
||||
## 4. Privacy review questions
|
||||
## Testing
|
||||
|
||||
**Browsing data or identifiers?** No intentional collection of URLs, domains visited, search queries, titles, page content, message payloads, user IDs, tab/document/context IDs, extension UUIDs or process IDs in these new payloads. Internal object/ID comparisons emit booleans. Error domains are error namespaces, not visited website domains, but are currently not allowlisted.
|
||||
- Unit tests: parameter serialization, buckets, timeline limits, attachment to failure/stuck pixels, reload-error mapping, delegate forwarding, ownership and runtime flag changes.
|
||||
- Integration validation on iOS/macOS: force background-process termination, check the emitted diagnostics, test proxy on/off and extension reload failures. Confirm unknown SPI values are omitted.
|
||||
- Privacy validation: inspect actual payloads and run both platform schema validators. Confirm URLs, identifiers and error messages are absent. Review exact-second timelines and error-domain/code values before release.
|
||||
|
||||
**Could events be linked or unusually specific?** No explicit correlation identifier is added. However, exact-second event sequences and arbitrary error-domain/code combinations can be high-cardinality. This draft does **not** assert that linkability is impossible.
|
||||
## Additional Considerations (if applicable)
|
||||
|
||||
**Sensitive user actions?** `reload_trigger=data_clearing` reveals that a failing reload followed data clearing. It does not identify cleared sites or data. This requires explicit triage consideration.
|
||||
**Privacy**
|
||||
|
||||
**Temporary or permanent?** Intended for diagnosis and the M4 validation gate. Current schemas do not implement an expiry/removal deadline; agree one before rollout. No change to the baseline health pixels' intended lifetime.
|
||||
New pixel and additional parameters require Privacy Triage. No intentional collection of browsing URLs, search queries, page content, message payloads, tab/context IDs or PIDs in these payloads. Exact-second timelines and unrestricted error-domain/code combinations can be high-cardinality; bucketing/allowlisting needs a decision. `data_clearing` discloses the operation preceding a failed reload.
|
||||
|
||||
**Self-service eligible?** Not asserted. Exact seconds conflict with the repository's coarse-bucketing guidance; error descriptors, the data-clearing trigger and retention/removal policy need a privacy decision.
|
||||
Current local diagnostics logs separately include raw errors with `privacy: .public`; remove/redact these before production. Pixel approval does not approve those logs.
|
||||
|
||||
## 5. Boundaries and release gates
|
||||
**Security**
|
||||
|
||||
**Local logs are separate from pixel payloads.** Current recorder code also logs process IDs and raw localized/underlying errors with `privacy: .public`. These logs may expose paths or URLs and must be removed/redacted before production; pixel approval does not approve them.
|
||||
Uses guarded private WebKit selectors for process/script facts. Validate supported OS versions; missing facts must not block pixel delivery.
|
||||
|
||||
**What these pixels cannot establish:** no denominator of all process deaths; deaths followed by normal recovery may produce no diagnostic pixel. A failing tab does not prove an all-tabs outage. The rolling timeline may lose the original trigger, and deferred snapshots can observe newer state.
|
||||
**Site Breakage**
|
||||
|
||||
**Observer effect:** the findings note records that installing the delegate proxy changed the forced-process-death reproduction. Validate the current proxy on/off on both platforms before treating M3 as an unaffected production baseline.
|
||||
No intended page-behavior changes. However, the investigation recorded a changed reproduction with the delegate proxy installed; validate the current proxy on/off before treating observations as an unaffected baseline.
|
||||
|
||||
**Approval requested:** necessity and format of the snapshot fields, coarse replacement or explicit exception for exact-second timelines, allowlisting/bounding of error descriptors, the data-clearing trigger, and a diagnostic sunset date.
|
||||
**Experimentation**
|
||||
|
||||
Implementation references: `CPMMessagingDiagnostics.swift`, `CPMMessagingDiagnosticsRecorder.swift`, `CPMBackgroundWebViewDelegateProxy.swift`, `WebExtensionPixelFiring.swift`; iOS/macOS pixel schemas. Investigation: [[cpm-web-extension-breakage-findings]]. Milestones: [[cpm-web-extension-breakage-validation]].
|
||||
None in M3. Graveyard mitigation and A/B testing remain separate milestones.
|
||||
|
||||
**Operational**
|
||||
|
||||
Update the existing CPM Grafana board for the new fields and reload failures. Agree a diagnostic removal/review date; none is enforced currently. Failure-only snapshots do not provide a denominator of all process deaths or prove an all-tabs outage. Rolling timelines can lose the trigger; deferred snapshots can observe newer state.
|
||||
|
||||
**Localization / Internationalization**
|
||||
|
||||
None.
|
||||
|
||||
Reference in New Issue
Block a user