debug-convergence

Разбор упавшего прогона симулятора или property-теста — воспроизвести по seed и трассе, минимизировать, найти причину (сначала подозревать симулятор, потом ядра), оформить регрессионную трассу и тест

SKILL.md

GitHub

debug-convergence — упал симулятор или property-тест

Упавший прогон — это либо баг в ядре (crdt, client-core, server-core), либо баг самого симулятора: неверная проверка, нереалистичный ввод, дыра в модели мира. Из первых пяти разобранных падений T-005 все пять оказались ошибками симулятора, поэтому порядок подозрений ниже — не формальность.

Процедура

  1. Зафиксировать падение. Свойство (S1…S11), шаг, seed, профиль, число клиентов, ops. Команда повтора печатается в отчёте (repro:), трасса лежит в .sim/fail-<seed>.json. Упал test:prop — npm run test:prop -- --seed=<n> и контрпример fast-check.
  2. Воспроизвести. npm run sim -- --replay=.sim/fail-<seed>.json обязан упасть на том же свойстве и шаге.
    • Не воспроизводится, свойство — не S9: дальше не идти, чинить расхождение воспроизведения (не сохранён идентификатор в решении, поток чисел расходуется не там), а не «баг».
    • Не воспроизводится, свойство — S9: это может быть настоящее ограничение replay (docs/spec/simulator.md § 9.2) — на отметке checkpoint он даёт drain() свежий, более щедрый бюджет, и временная нехватка бюджета (доставки сходились бы, просто не в старый B) незаметно «исцеляется», а структурная причина (честное зависание, мутант) — нет. Не считать «REPLAY OK» доказательством отсутствия бага: разобрать вручную (шаг ниже), а не останавливаться.
    • Разбор вручную (только для этого случая S9): маленьким скриптом (не трогая src/) через публичный API createWorld/step/isApplicable/drainBudget/drainResidue пройти записанные решения трассы до конца и посчитать drainBudget(world) на входе в упавший раунд drain (граница раунда — последнее решение вида не connect/serverNotice/deliver перед финальным checkpoint); сравнить с числом deliver в раунде из сообщения об ошибке. Если раунд почти сошёлся (канал линейно убывает, не растёт) — недостающая формула бюджета; если конкретное сообщение вызвало резкий скачок канала (+N за один deliver) — искать механизм всплеска (так нашли reveal-catchup, § 7 SIM-07).
  3. Минимизировать. npm run sim -- --replay=<трасса> --minimize (бюджет --minimize-budget, по умолчанию 2000 прогонов). Результат — <трасса>.min.json и журнал шагов: обычно от 2 до 20 решений вместо тысяч. Оговорка: ddmin сохраняет свойство, а не причину — минимальная трасса может показывать другую поломку, чем исходная. После исправления проверить и исходную полную трассу.
  4. Прочитать отчёт. Для S4 в нём клиент, его актор, чего не хватает в X_c и что лишнее, последние события этого клиента (missing in X_c / extra in X_c). Сопоставить с журналом шагов минимальной трассы.
  5. Найти причину — в таком порядке подозрений:
    1. проверка симулятора (checks.ts, оракул): ложное срабатывание бывает, когда проверка не учитывает законное поведение ядра (потерянные и повторённые reject, dot unvote = dot голоса по ADR-0010, закрытие соединения сервером);
    2. мир и генератор (world.ts, apply.ts, intents.ts): нереалистичный ввод (действие над тем, чего клиент не знает, restore чужого стикера, команда до welcome), дыра в модели сети или досылки;
    3. ядра — только когда 1 и 2 исключены. Доказывать трассировкой конкретного actor/dot (временный лог по env-флагу, одно реальное выполнение), а не гипотезой. Временные правки откатывать точечно, а не git checkout файла, где есть чужие некоммитированные изменения.
  6. Красный тест первым. Минимальную трассу положить в packages/sim/test/regressions/<имя>.json (npm run sim -- --replay=<трасса> --minimize --out=packages/sim/test/regressions/<имя>.json), она должна падать на коде с багом; плюс точечный тест в пакете, которому принадлежит причина. Только потом исправление.
  7. Исправить минимально и прогнать: регрессионные трассы (test/regressions.test.ts), npm run test:sim (включая мутанты SIM-12 — если правилась проверка, она не должна перестать ловить своих мутантов), затем npm run check.
  8. Журнал. /journal: причина, кто оказался виноват (симулятор или ядро), как доказано.

Чего не делать

  • Не ослаблять проверку и не менять seed/ops, чтобы прогон позеленел (правило 3 CLAUDE.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