Files
obsidian-vault/personal/tech/roundtrip-key-verification.md
T

103 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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. Питфолл «две точки входа» — чтобы следующая сессия сразу проверяла обе.