debug-convergence
Разбор упавшего прогона симулятора или property-теста — воспроизвести по seed и трассе, минимизировать, найти причину (сначала подозревать симулятор, потом ядра), оформить регрессионную трассу и тест
SKILL.md.claude/skills/debug-convergence/SKILL.md
GitHubdebug-convergence — упал симулятор или property-тест
Упавший прогон — это либо баг в ядре (crdt, client-core, server-core), либо баг самого симулятора: неверная проверка, нереалистичный ввод, дыра в модели мира. Из первых пяти разобранных падений T-005 все пять оказались ошибками симулятора, поэтому порядок подозрений ниже — не формальность.
Процедура
- Зафиксировать падение. Свойство (
S1…S11), шаг,seed, профиль, число клиентов,ops. Команда повтора печатается в отчёте (repro:), трасса лежит в.sim/fail-<seed>.json. Упалtest:prop—npm run test:prop -- --seed=<n>и контрпример fast-check. - Воспроизвести.
npm run sim -- --replay=.sim/fail-<seed>.jsonобязан упасть на том же свойстве и шаге.- Не воспроизводится, свойство — не S9: дальше не идти, чинить расхождение воспроизведения (не сохранён идентификатор в решении, поток чисел расходуется не там), а не «баг».
- Не воспроизводится, свойство — S9: это может быть настоящее ограничение replay (docs/spec/simulator.md § 9.2) — на отметке
checkpointон даётdrain()свежий, более щедрый бюджет, и временная нехватка бюджета (доставки сходились бы, просто не в старыйB) незаметно «исцеляется», а структурная причина (честное зависание, мутант) — нет. Не считать «REPLAY OK» доказательством отсутствия бага: разобрать вручную (шаг ниже), а не останавливаться. - Разбор вручную (только для этого случая S9): маленьким скриптом (не трогая
src/) через публичный APIcreateWorld/step/isApplicable/drainBudget/drainResidueпройти записанные решения трассы до конца и посчитатьdrainBudget(world)на входе в упавший раундdrain(граница раунда — последнее решение вида неconnect/serverNotice/deliverперед финальнымcheckpoint); сравнить с числомdeliverв раунде из сообщения об ошибке. Если раунд почти сошёлся (канал линейно убывает, не растёт) — недостающая формула бюджета; если конкретное сообщение вызвало резкий скачок канала (+Nза одинdeliver) — искать механизм всплеска (так нашли reveal-catchup, § 7 SIM-07).
- Минимизировать.
npm run sim -- --replay=<трасса> --minimize(бюджет--minimize-budget, по умолчанию 2000 прогонов). Результат —<трасса>.min.jsonи журнал шагов: обычно от 2 до 20 решений вместо тысяч. Оговорка: ddmin сохраняет свойство, а не причину — минимальная трасса может показывать другую поломку, чем исходная. После исправления проверить и исходную полную трассу. - Прочитать отчёт. Для S4 в нём клиент, его актор, чего не хватает в
X_cи что лишнее, последние события этого клиента (missing in X_c/extra in X_c). Сопоставить с журналом шагов минимальной трассы. - Найти причину — в таком порядке подозрений:
- проверка симулятора (
checks.ts, оракул): ложное срабатывание бывает, когда проверка не учитывает законное поведение ядра (потерянные и повторённыеreject,dotunvote=dotголоса по ADR-0010, закрытие соединения сервером); - мир и генератор (
world.ts,apply.ts,intents.ts): нереалистичный ввод (действие над тем, чего клиент не знает,restoreчужого стикера, команда доwelcome), дыра в модели сети или досылки; - ядра — только когда 1 и 2 исключены.
Доказывать трассировкой конкретного actor/dot (временный лог по env-флагу, одно реальное выполнение), а не гипотезой. Временные правки откатывать точечно, а не
git checkoutфайла, где есть чужие некоммитированные изменения.
- проверка симулятора (
- Красный тест первым. Минимальную трассу положить в
packages/sim/test/regressions/<имя>.json(npm run sim -- --replay=<трасса> --minimize --out=packages/sim/test/regressions/<имя>.json), она должна падать на коде с багом; плюс точечный тест в пакете, которому принадлежит причина. Только потом исправление. - Исправить минимально и прогнать: регрессионные трассы (
test/regressions.test.ts),npm run test:sim(включая мутанты SIM-12 — если правилась проверка, она не должна перестать ловить своих мутантов), затемnpm run check. - Журнал.
/journal: причина, кто оказался виноват (симулятор или ядро), как доказано.
Чего не делать
- Не ослаблять проверку и не менять
seed/ops, чтобы прогон позеленел (правило 3CLAUDE.md). Если проверка неверна — сначала правка спецификации отдельным коммитом с объяснением. - Не судить об ускорении или замедлении по одиночному замеру: разброс ±10 % на этой машине.
- Не принимать зелёный результат мутанта или отключённой досылки за доказательство, пока не проверено, что подмена вообще попала в код (
grepпо собранномуdist).
Справка
| Что | Команда |
|---|---|
| прогон | npm run sim -- --clients=5 --ops=10000 --seed=<n> [--profile=...] |
| воспроизвести | npm run sim -- --replay=<трасса> |
| минимизировать | npm run sim -- --replay=<трасса> --minimize [--out=...] |
| пересобрать трассы мутантов | npm run sim:regressions |
| длинный набор | npm run sim:long |