8.3 KiB
BRD — ORCH-065: zombie jobs + залипший merge-lease
Work Item ID: ORCH-065 Тип: BUG (P0) Репозиторий: orchestrator (self-hosting) Эпик: блокер ORCH-54 (полностью автономный self-deploy)
1. Контекст и проблема
Оркестратор — единый инстанс с общей БД и общей очередью (jobs,
max_concurrency=1 для self-hosting), обслуживающий несколько проектов. Финальная
автономность self-deploy упирается в два связанных класса отказов, оба сводящиеся
к «процесс умер/завершился, а состояние осталось захваченным навсегда»:
Проблема A — zombie jobs (строка jobs навсегда running)
Агент (deployer/developer/reviewer) завершается или умирает (краш, OOM,
рестарт контейнера в ходе self-deploy, гибель monitor-потока), но строка в таблице
jobs остаётся в статусе running. Финализация статуса job выполняется только
в _monitor_agent → _finalize_job внутри того же процесса; если этот поток/процесс
не доживает до финализации — job «зомбирован».
- Единственная имеющаяся защита —
requeue_running_jobs()вmain.lifespan, срабатывающая исключительно на старте процесса. Зомби, возникший без рестарта (умер дочерний процесс/monitor-поток, а сервис жив), не реанимируется никогда. - При
max_concurrency=1одна зомби-строкаrunningблокирует claim всех последующих job (count_running_jobs() >= max_concurrency→ claim не происходит) → встаёт конвейер всех проектов.
Проблема B — залипший merge-lease
Merge-gate (ORCH-043) берёт файловый lease <repos_dir>/.merge-lease-<repo>.json
ПЕРЕД rebase+re-test и держит его до фактического merge PR в main. Если процесс
умирает на финальном merge с зажатым lease:
- Реклейм lease реализован лениво и только по возрасту (
age >= merge_lock_timeout_s) и только в моментacquire_merge_leaseдругой задачей. Проактивного освобождения (на старте / периодически) нет; liveness держателя по pid не проверяется (хотяpidв lease пишется). - Пострадавшая задача сама re-drive не получает: merge не финализируется → задача висит, lease мешает чужим merge до истечения TTL.
Проблема C — неидемпотентная финализация merge
Если rebase+re-test прошли зелёно (ветка догнана и проверена), но процесс умер до завершения слияния PR — повторного «докатывания» merge нет. Задача застревает в полу-выполненном состоянии, хотя вся дорогая работа (rebase+re-test) уже сделана.
2. Бизнес-последствия
- Это ПОСЛЕДНЯЯ ручная точка автономного деплоя. Без фикса ни одна self-hosting
задача не доезжает до прода без оператора (cancel zombie + ручной merge PR +
ручной
--deploy). - Прямой блокер эпика ORCH-54.
- Доказанные инциденты (07.06): ORCH-58/60/61/21 — каждый раз после успешного
deployer-прохода job оставался
running; jobs 236/239/242/254 — зомби, прод-merge/deploy доводились вручную. - Групповой риск: зомби в общей очереди при concurrency=1 останавливает конвейер enduro-trails и всех прочих проектов.
3. Цель
Сделать так, чтобы смерть процесса/потока на любой стадии (включая self-restart
во время deploy) НЕ оставляла навсегда захваченных ресурсов — ни строки jobs в
running, ни merge-lease. Конвейер должен самовосстанавливаться без оператора, при
этом сохраняя все инварианты self-hosting (не ронять прод-контейнер, не трогать
main, fail-closed на реальных ошибках).
4. Объём (Scope)
В объёме
- Job-reaper — фоновый watchdog (паттерн
reconciler/queue_worker), детектирующий «мёртвый»running-job и приводящий его строку в корректный терминальный/повторный статус (done/failed/queued) детерминированно, без LLM. Restart-safe и работающий без рестарта процесса. - Проактивный реклейм stale merge-lease — освобождение lease, чей держатель
мёртв (pid не жив) ИЛИ возраст превысил TTL — на старте и периодически (reaper/
reconciler), а не только лениво при чужом
acquire. - Идемпотентная финализация merge — если rebase+re-test зелёные, но merge не состоялся, операция повторяется/докатывается без потери уже сделанной работы.
Вне объёма
- Переход на внешний брокер очередей / смену схемы блокировок merge на БД-lock.
- Полный авто-approve деплоя (ORCH-54) — отдельная задача; здесь только снятие технического блокера.
- Изменение конвейера стадий (
STAGE_TRANSITIONS) и реестра гейтов как контрактов.
5. Заинтересованные стороны
- Owner оркестратора (self-hosting автономность).
- Все проекты на общем инстансе (enduro-trails и пр.) — страдают от блокировки общей очереди.
6. Допущения и ограничения
max_concurrency=1для self-hosting сохраняется.- Self-hosting safety (CLAUDE.md): нельзя ронять/рестартить прод-контейнер в рамках
задачи; нельзя пушить/форс-пушить
main; реклейм lease не должен прерывать легитимно работающий merge. - Никаких ложных реанимаций: живой, но долгий job не должен помечаться зомби (нужен порог/грейс «N тиков» + проверка реальной смерти, а не просто долготы).
- Контракт never-raise для всей новой фоновой логики (как у reconciler/merge_gate).
- Kill-switch на каждый новый механизм (как
reconcile_enabled/merge_gate_enabled).
7. Критерий успеха (бизнес-уровень)
После фикса воспроизводимый сценарий «успешный deployer-проход + смерть процесса/
self-restart» НЕ оставляет зомби-job и зажатого lease: задача либо корректно
доезжает до done сама, либо откатывается по штатному контракту — без участия
оператора. Регресс-тест на jobs-зомби и stale-lease зелёный.