[2026-09-17] eagle: personal/projects/zont-config-compiler.md personal/tech/roundtrip-key-verification.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 20:02:47 +06:00
parent 5fbf03b00f
commit 97ba40bf8e
2 changed files with 202 additions and 42 deletions
+102
View File
@@ -0,0 +1,102 @@
---
title: 🔁 Round-trip key verification — проверка нового ключа конвертера
namespace: personal
type: how-to
created: '2026-09-17'
updated: '2026-09-17'
tags:
- personal
- how-to
- testing
- converters
- reference
related:
- '[[personal/projects/zont-config-compiler]]'
---
# 🔁 Round-trip key verification
> Зеркало навыка `roundtrip-key-verification` (категория `software-development`, Hermes).
> Появился из реального бага в ZONT-конвертере 2026-09-17 (коммит `87e315c`).
> Разбор в проекте — [[personal/projects/zont-config-compiler]] §10.1.
## Когда применять
Любой двусторонний конвертер с тестом байтовой точности: `.txt ⇄ .yml`, сериализатор с
decode + encode, dump/undump AST, DB ⇄ ORM.
**Триггер:** добавил **новый ключ** в декодированный вывод и round-trip зелёный.
## 🔴 Ловушка
Байтовый round-trip доказывает, что **вывод парсера == вход**. Про энкодер он не говорит
ничего — энкодер получает ровно то, что парсер положил.
Если энкодер **игнорирует** новый ключ, тест всё равно зелёный:
```
original.txt → parse → YAML(с новым ключом) → encode(ключ игнорируется) → original.txt ✅ diff = 0
▲ ключ добавлен ▲ ключ потерян, старое поле доносит байты
```
Потеря невидима: старое поле (`args`) рядом и по-прежнему собирает байты.
**Реальный случай:** ключ `set_var` добавлен в парсер типа 59, энкодер поправлен **только**
в `emit_action`. Вторая точка входа — инлайн-ветка `emit_step` (`'descr' in step and 'args' in step`) —
ключ игнорировала. Round-trip 🟢, правка пользователя молча терялась.
## Обязательный шаг: тест подменой (mutation)
`read back` («записалось») — **не** проверка. Подменить новое значение и пересобрать; эффект
обязан появиться в выходе.
```bash
cd /tmp && rm -rf t && mkdir t && cd t
cp <реальный-артефакт>.yml t.yml
python3 - <<'EOF'
s = open('t.yml', encoding='utf-8').read()
old = "...блок со старым значением нового ключа..."
new = "...тот же блок, значение заменено на маркер (напр. 7777)..."
assert s.count(old) == 1, s.count(old) # assert уникальности — без случайных мультиправок
open('t.yml', 'w', encoding='utf-8').write(s.replace(old, new))
EOF
python3 <проект>/encode.py t.yml > out.txt
diff <(iconv -f <вх> -t utf-8 <оригинал> | tr -d '\r' | sort) \
<(iconv -f <вых> -t utf-8 out.txt | tr -d '\r' | sort)
# ✅ изменилась РОВНО одна строка, в ней маркер
# ❌ ноль изменений = энкодер ключ проигнорировал → искать ДРУГУЮ ветку кода
```
Проверять **обе** половины:
1. **Парсер** — ключ есть в промежуточном файле, на ожидаемом месте.
2. **Энкодер** — подмена доезжает до выхода, `diff` = ровно 1 строка.
## Найти ВСЕ точки входа
У конвертеров их обычно две и больше: рекурсивный обход и инлайн/ссылочная ветка, либо
проход по реестру типов и явный цикл.
- `grep -n "def emit_<x>\|def dump_<x>" *.py` — выписать **всех** вызывающих.
- Не править одну и полагать, что покрыта другая. Дублированные ветки — норма.
- Симптом пропущенной ветки: в тесте подменой остаётся старое значение.
В **каждой** ветке ставить проверку типа, чтобы плохое значение падало громко (`exit 2`),
а не тихо уходило сырым полем.
## Смежные питфоллы
- **Проверять тип производного ключа.** Парсер, сохраняющий whole-float как float (`14.5`, `1.0`),
даст не-int для ключа-идентификатора. Guard:
`isinstance(v, int) and not isinstance(v, bool) and not isinstance(v, float) and v != 0`.
`0` = «цели нет», а не валидный id — пропускать, не выдумывать.
- **Один объект в двух секциях** → ключ править во **всех** местах отображения, иначе одна
проекция показывает новую семантику, другая старую. Round-trip и тут зелёный.
- **Считать вхождения по данным**, не по плану: «12 элементов» из плана оказались 10 в конфиге.
- **Не резолвить имя из другого объекта.** Производный ключ несёт сырой id; `target_name`
вместо `target` — запрещённая подстановка.
## Что записать после проверки
1. Новый ключ, где эмитится, какие точки входа поправлены.
2. Команду подмены и наблюдённый результат («1 строка изменилась, маркер на месте»).
3. Питфолл «две точки входа» — чтобы следующая сессия сразу проверяла обе.