92811c258f
ReconnectSupervisor был написан, протестирован и экспортнут, но НИГДЕ не подключён к циклу чтения. Parser.start() имел голый for-await без try/catch: падение ноды → ShipConnectionError наверх → темп переподключений целиком зависел от внешнего супервизора процесса. Мёртвая нода = рестарт без пауз = долбёжка ноды без ограничителей. Что сделано: - Цикл чтения вынесен в runStreamLoop (core/streamLoop.ts) с инъекцией зависимостей — чтобы покрыть reconnect/resume юнит-тестами на фейках, без реального Redis и SHiP. - runStreamLoop оборачивает (пере)подключение в ReconnectSupervisor: backoff 1→2→5→15→60с, после maxAttempts без прогресса — process.exit(1) (оркестратор перезапустит штатно). - Позиция возобновления перечитывается из Redis на КАЖДОМ подключении (sync-hash пишется после каждого блока) — продолжаем ровно с последнего записанного блока, без потерь и дублей. - ReconnectSupervisor.run теперь передаёт fn callback resetBackoff: каждый обработанный блок сбрасывает счётчик попыток, чтобы долгая стабильная сессия после редкого разрыва не копила attempt до выхода. - Штатная остановка (stop закрывает ws) ловится и не вызывает лишний backoff. - Parser получил структурный логгер (opts.logger) для onAttempt/onGiveUp. Тесты: +4 runStreamLoop (reconnect+resume, stop без reconnect, give-up на мёртвой ноде, resetBackoff на прогрессе), +4 ReconnectSupervisor (resetBackoff). Всего 229 unit. README: секция «Авто-переподключение к SHiP». Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>