[2026-09-18] eagle: personal/projects/zont-config-compiler.md personal/tech/roundtrip-key-verification.md
This commit is contained in:
@@ -221,3 +221,102 @@ grep -c '^\- id:' out.yml.after
|
||||
```
|
||||
|
||||
Рост этой секции = форма не разобрана, а не «данных нет».
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Авто-генерация id: обернуть сборку строки НЕДОСТАТОЧНО (2026-09-18, ZONT круг 50)
|
||||
|
||||
**Контекст:** учим энкодер **выдумывать** id записям, у которых его нет в промежуточном файле.
|
||||
Пять ловушек, все найдены на живом коде.
|
||||
|
||||
### Ловушка 1 — `keep()` и `alloc()` пишут в РАЗНЫЕ хранилища занятости
|
||||
|
||||
У аллокатора было два хранилища: `_used` (зарезервированные) и `renames` (старый → новый).
|
||||
`keep(id)` писал только в `_used`, а резолвер смотрел в `renames` → **свой** id выглядел
|
||||
незанятым → выдавался новый номер → строка уезжала под новым id, а **ссылки** в полях
|
||||
оставались на старом.
|
||||
|
||||
```
|
||||
round-trip: 759 → 693, ровно −66 строк (все одного типа)
|
||||
симптом: тело выпущено с НОВЫМ id, ссылки несут СТАРЫЙ → объект молча осиротел
|
||||
```
|
||||
|
||||
**Правило:** одно хранилище, одна функция. `keep(id)` регистрирует **тождественное
|
||||
переименование** `id -> id`. Любой помощник, помечающий id занятым, обязан писать и в карту
|
||||
переименований. Два хранилища = гарантированное расхождение.
|
||||
|
||||
### Ловушка 2 — барьер `if 'id' in obj` отсекает запись ДО аллокатора
|
||||
|
||||
```python
|
||||
for obj in obj_list:
|
||||
if isinstance(obj, dict) and 'id' in obj: # ← запись без id сюда не входит
|
||||
zid = resolve_id(obj['id'])
|
||||
```
|
||||
|
||||
Запись исчезает **при `exit=0`** и не появляется **ни под старым, ни под новым** id. Такой же
|
||||
барьер обычно стоит в нескольких циклах — обход типов, обход орфанов/секций, индекс тел:
|
||||
|
||||
```bash
|
||||
grep -n "'id' in \|\"id\" in \|'id' not in \|get('id')" encoder.py
|
||||
```
|
||||
|
||||
**Правило:** при внедрении авто-генерации обернуть `build_line`/`add_z_line` — только половина
|
||||
работы. Нужно снять **условия допуска** во **всех** циклах вывода, иначе запись не дойдёт до
|
||||
аллокатора.
|
||||
|
||||
**Диагностический признак ловушки 2:** `exit=0`, в stderr пусто, строка отсутствует и под
|
||||
старым, и под новым id. Если новый id всё-таки выдан — это ловушка 1, а не 2.
|
||||
|
||||
### Ловушка 3 — «id есть в файле» ≠ «запись можно оставить без id»
|
||||
|
||||
Замерено: снять id у одной записи каждой секции и пересобрать.
|
||||
|
||||
| Класс записи | Можно без id? |
|
||||
|---|---|
|
||||
| **Лист** — на неё никто не ссылается (ни по id, ни по типу) | ✅ да |
|
||||
| На неё ссылаются **по типу** (`body_type(id)` для событий/параметров) | ❌ нет — тип ищется **по id** |
|
||||
| В секции, чей цикл требует `id` у каждой записи | ❌ нет без ослабления цикла |
|
||||
|
||||
Безопасный первый заход — **только листья**. Записи, чей тип кто-то запрашивает, требуют, чтобы
|
||||
тип выводился **до** присвоения id — это отдельная правка.
|
||||
|
||||
### Ловушка 4 — ссылка в никуда НЕ проверяется (опасная половина)
|
||||
|
||||
```yaml
|
||||
- id: 8470
|
||||
set_relay:
|
||||
target: 99999 # ← объекта с таким id нет
|
||||
```
|
||||
|
||||
Сборка **прошла, `exit=0`**, и `99999` уехал в выходной конфиг. Контроллер получил бы команду
|
||||
в никуда. **Это опаснее потерянного объекта.**
|
||||
|
||||
`validate_config` проверяет кодируемость и дубли id — **ссылки он не резолвит**. Вместе с
|
||||
авто-генерацией нужна валидация ссылок: каждое `target` / `output_id` / `object` / `left` /
|
||||
`right` / список-id обязано быть в множестве существующих id, иначе падение.
|
||||
Приёмка: подменить одну ссылку на свободный id и убедиться, что сборка **упала**.
|
||||
|
||||
### Ловушка 5 — проверять допущение МИНИ-КЕЙСОМ, а не чтением кода
|
||||
|
||||
Всё вышеперечисленное найдено **трассировкой одной записи на минимальном входе** (один сценарий,
|
||||
`build_line` обёрнут логированием id), а не чтением эмиттера. Трассировка напечатала `id=4098`
|
||||
там, где в источнике `8550` — это и назвало баг одной строкой.
|
||||
|
||||
```python
|
||||
orig = mod.build_line
|
||||
seen = []
|
||||
mod.build_line = lambda t, i, f: (seen.append(i), orig(t, i, f))[1]
|
||||
mod.convert(minimal_subset) # один сценарий / одна запись
|
||||
print(seen) # сверить с id, которые есть на входе
|
||||
```
|
||||
|
||||
**Проверка подозрения на регресс — против чистого HEAD, до обвинения своей правки:**
|
||||
|
||||
```bash
|
||||
git stash push -u -m wip # работа спасается только stash'ем
|
||||
python3 test_roundtrip.py # базовая линия на чистом HEAD
|
||||
git stash pop
|
||||
```
|
||||
|
||||
База тоже сломана → унаследовано; база зелёная → это твоя правка. И **коммитить сразу, как
|
||||
только круг зелёный** — `git checkout --` затирает незакоммиченное без возврата.
|
||||
|
||||
Reference in New Issue
Block a user